mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-10 08:38:30 +00:00
feat(stats): add clickable section headings (#34095)
This commit is contained in:
parent
7b1fe33ed3
commit
82f47cb312
5 changed files with 169 additions and 45 deletions
|
|
@ -30,6 +30,7 @@ import {
|
|||
type ModelCatalogCost,
|
||||
type ModelCatalogEntry,
|
||||
} from "../model-catalog"
|
||||
import { SectionHeading } from "../section-heading"
|
||||
import { runStatsEffect } from "../../stats-runtime"
|
||||
import { setStatsPageCacheHeaders } from "../stats-cache"
|
||||
import {
|
||||
|
|
@ -205,7 +206,11 @@ function ModelLoading() {
|
|||
<a data-slot="model-back-link" href={language.route(import.meta.env.BASE_URL)}>
|
||||
{i18n.t("footer.modelData")}
|
||||
</a>
|
||||
<h1>{i18n.t("model.loadingTitle")}</h1>
|
||||
<h1>
|
||||
<a data-slot="heading-link" href="#overview">
|
||||
{i18n.t("model.loadingTitle")}
|
||||
</a>
|
||||
</h1>
|
||||
<p>{i18n.t("model.loadingDescription")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -228,7 +233,11 @@ function ModelNotFound(props: { lab: string; model: string }) {
|
|||
<a data-slot="model-back-link" href={language.route(import.meta.env.BASE_URL)}>
|
||||
{i18n.t("footer.modelData")}
|
||||
</a>
|
||||
<h1>{props.model || i18n.t("model.fallback")}</h1>
|
||||
<h1>
|
||||
<a data-slot="heading-link" href="#overview">
|
||||
{props.model || i18n.t("model.fallback")}
|
||||
</a>
|
||||
</h1>
|
||||
<p>{i18n.t("model.noMatched", { id: props.lab ? `${props.lab}/${props.model}` : props.model })}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -260,7 +269,11 @@ function ModelHero(props: { data: StatsModelData | null; catalog: ModelCatalogEn
|
|||
</a>
|
||||
<span data-slot="model-id-tag">{modelId()}</span>
|
||||
</div>
|
||||
<h1>{props.catalog?.name ?? props.data?.model ?? i18n.t("model.fallback")}</h1>
|
||||
<h1>
|
||||
<a data-slot="heading-link" href="#overview">
|
||||
{props.catalog?.name ?? props.data?.model ?? i18n.t("model.fallback")}
|
||||
</a>
|
||||
</h1>
|
||||
<Show when={props.data} fallback={<p>{i18n.t("model.catalogFallback")}</p>}>
|
||||
{(data) => (
|
||||
<p>
|
||||
|
|
@ -352,8 +365,12 @@ function CatalogDatum(props: { label: string; value: string }) {
|
|||
function ModelOverview(props: { data: StatsModelData | null }) {
|
||||
const i18n = useI18n()
|
||||
return (
|
||||
<section data-section="model-panel">
|
||||
<SectionTitle title={i18n.t("nav.overview")} description={i18n.t("model.overviewDescription")} />
|
||||
<section id="model-overview" data-section="model-panel">
|
||||
<SectionTitle
|
||||
href="#model-overview"
|
||||
title={i18n.t("nav.overview")}
|
||||
description={i18n.t("model.overviewDescription")}
|
||||
/>
|
||||
<Show
|
||||
when={props.data}
|
||||
fallback={
|
||||
|
|
@ -399,7 +416,7 @@ function ModelUsageSection(props: { data: ModelUsagePoint[] }) {
|
|||
const i18n = useI18n()
|
||||
return (
|
||||
<section id="usage" data-section="model-panel">
|
||||
<SectionTitle title={i18n.t("nav.usage")} description={i18n.t("model.usageDescription")} />
|
||||
<SectionTitle href="#usage" title={i18n.t("nav.usage")} description={i18n.t("model.usageDescription")} />
|
||||
<Show
|
||||
when={props.data.some((item) => item.tokens > 0)}
|
||||
fallback={
|
||||
|
|
@ -416,7 +433,7 @@ function ModelUsersSection(props: { data: ModelUsagePoint[] }) {
|
|||
const i18n = useI18n()
|
||||
return (
|
||||
<section id="users" data-section="model-panel">
|
||||
<SectionTitle title={i18n.t("model.uniqueUsers")} description={i18n.t("model.usersDescription")} />
|
||||
<SectionTitle href="#users" title={i18n.t("model.uniqueUsers")} description={i18n.t("model.usersDescription")} />
|
||||
<Show
|
||||
when={props.data.some((item) => item.users > 0)}
|
||||
fallback={
|
||||
|
|
@ -550,7 +567,11 @@ function ModelEfficiencySection(props: { data: StatsModelData | null; catalog: M
|
|||
const i18n = useI18n()
|
||||
return (
|
||||
<section id="efficiency" data-section="model-panel">
|
||||
<SectionTitle title={i18n.t("nav.efficiency")} description={i18n.t("model.efficiencyDescription")} />
|
||||
<SectionTitle
|
||||
href="#efficiency"
|
||||
title={i18n.t("nav.efficiency")}
|
||||
description={i18n.t("model.efficiencyDescription")}
|
||||
/>
|
||||
<Show
|
||||
when={props.data}
|
||||
fallback={
|
||||
|
|
@ -623,7 +644,11 @@ function ModelGeoBreakdownSection(props: { data: Record<UsageRange, CountryEntry
|
|||
setActiveCountry(undefined)
|
||||
}}
|
||||
>
|
||||
<SectionTitle title={i18n.t("nav.geoBreakdown")} description={i18n.t("model.geoDescription")} />
|
||||
<SectionTitle
|
||||
href="#geo-breakdown"
|
||||
title={i18n.t("nav.geoBreakdown")}
|
||||
description={i18n.t("model.geoDescription")}
|
||||
/>
|
||||
<Show
|
||||
when={data().length > 0}
|
||||
fallback={<ModelEmptyState title={i18n.t("model.noGeoTitle")} description={i18n.t("model.noGeoDescription")} />}
|
||||
|
|
@ -788,7 +813,7 @@ function ModelPeersSection(props: { data: StatsModelData | null }) {
|
|||
const i18n = useI18n()
|
||||
return (
|
||||
<section id="peers" data-section="model-panel">
|
||||
<SectionTitle title={i18n.t("nav.peers")} description={i18n.t("model.peersDescription")} />
|
||||
<SectionTitle href="#peers" title={i18n.t("nav.peers")} description={i18n.t("model.peersDescription")} />
|
||||
<Show
|
||||
when={props.data?.peers.length}
|
||||
fallback={
|
||||
|
|
@ -833,12 +858,8 @@ function PeerRow(props: { peer: ModelPeerEntry; active: boolean }) {
|
|||
)
|
||||
}
|
||||
|
||||
function SectionTitle(props: { title: string; description: string }) {
|
||||
return (
|
||||
<p data-slot="section-title">
|
||||
<strong>{props.title}.</strong> <span>{props.description}</span>
|
||||
</p>
|
||||
)
|
||||
function SectionTitle(props: { href: string; title: string; description: string }) {
|
||||
return <SectionHeading href={props.href} title={props.title} description={props.description} />
|
||||
}
|
||||
|
||||
function ModelEmptyState(props: { title: string; description: string; compact?: boolean }) {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
type ModelCatalogEntry,
|
||||
type ModelCatalogLab,
|
||||
} from "../model-catalog"
|
||||
import { SectionHeading } from "../section-heading"
|
||||
import { runStatsEffect } from "../../stats-runtime"
|
||||
import { setStatsPageCacheHeaders } from "../stats-cache"
|
||||
import {
|
||||
|
|
@ -148,7 +149,11 @@ function LabLoading() {
|
|||
<a data-slot="model-back-link" href={language.route(import.meta.env.BASE_URL)}>
|
||||
{i18n.t("footer.modelData")}
|
||||
</a>
|
||||
<h1>{i18n.t("lab.loadingTitle")}</h1>
|
||||
<h1>
|
||||
<a data-slot="heading-link" href="#overview">
|
||||
{i18n.t("lab.loadingTitle")}
|
||||
</a>
|
||||
</h1>
|
||||
<p>{i18n.t("lab.loadingDescription")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -166,7 +171,11 @@ function LabNotFound(props: { lab: string }) {
|
|||
<a data-slot="model-back-link" href={language.route(import.meta.env.BASE_URL)}>
|
||||
{i18n.t("footer.modelData")}
|
||||
</a>
|
||||
<h1>{formatCatalogLabName(props.lab)}</h1>
|
||||
<h1>
|
||||
<a data-slot="heading-link" href="#overview">
|
||||
{formatCatalogLabName(props.lab)}
|
||||
</a>
|
||||
</h1>
|
||||
<p>{i18n.t("lab.notFound")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -193,7 +202,11 @@ function LabHero(props: { lab: ModelCatalogLab; stats: StatsLabData | null }) {
|
|||
</a>
|
||||
<div data-slot="model-hero-grid">
|
||||
<div data-slot="model-hero-copy">
|
||||
<h1>{props.lab.name}</h1>
|
||||
<h1>
|
||||
<a data-slot="heading-link" href="#overview">
|
||||
{props.lab.name}
|
||||
</a>
|
||||
</h1>
|
||||
<div data-slot="model-hero-pattern" aria-hidden="true" />
|
||||
<p>
|
||||
{i18n.t("lab.heroPrefix", { count: props.lab.models.length, lab: props.lab.name })}
|
||||
|
|
@ -235,10 +248,11 @@ function LabUsageSection(props: { lab: ModelCatalogLab; data: StatsLabData | nul
|
|||
|
||||
return (
|
||||
<section id="usage" data-section="model-panel">
|
||||
<p data-slot="section-title">
|
||||
<strong>{i18n.t("lab.usageTitle", { lab: props.lab.name })}.</strong>{" "}
|
||||
<span>{i18n.t("lab.usageDescription")}</span>
|
||||
</p>
|
||||
<SectionHeading
|
||||
href="#usage"
|
||||
title={i18n.t("lab.usageTitle", { lab: props.lab.name })}
|
||||
description={i18n.t("lab.usageDescription")}
|
||||
/>
|
||||
<Show
|
||||
when={usage().some((item) => item.tokens > 0)}
|
||||
fallback={<LabEmptyState title={i18n.t("lab.noUsageTitle")} description={i18n.t("lab.noUsageDescription")} />}
|
||||
|
|
@ -336,10 +350,11 @@ function LabModelsSection(props: { lab: ModelCatalogLab; usage: LabUsageModelEnt
|
|||
const usageBySlug = createMemo(() => new Map(props.usage.map((item) => [item.slug, item])))
|
||||
return (
|
||||
<section id="models" data-section="model-panel">
|
||||
<p data-slot="section-title">
|
||||
<strong>{i18n.t("lab.modelsTitle", { lab: props.lab.name })}.</strong>{" "}
|
||||
<span>{i18n.t("lab.recentUsageAndLimits")}</span>
|
||||
</p>
|
||||
<SectionHeading
|
||||
href="#models"
|
||||
title={i18n.t("lab.modelsTitle", { lab: props.lab.name })}
|
||||
description={i18n.t("lab.recentUsageAndLimits")}
|
||||
/>
|
||||
<div data-component="lab-model-grid">
|
||||
<For each={props.lab.models}>
|
||||
{(model) => <LabModelCard model={model} usage={usageBySlug().get(model.slug)} />}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,11 @@
|
|||
display: none !important;
|
||||
}
|
||||
|
||||
[data-page="stats"] section[id],
|
||||
[data-page="stats"] [data-component="leaderboard"][id] {
|
||||
scroll-margin-top: 88px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="content"] {
|
||||
color: var(--stats-text);
|
||||
font-family:
|
||||
|
|
@ -1781,6 +1786,35 @@
|
|||
font-weight: 400;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="heading-link"] {
|
||||
position: relative;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="heading-link"]:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="heading-link"]:focus-visible {
|
||||
outline: 1px solid var(--stats-accent);
|
||||
outline-offset: 4px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="heading-anchor"] {
|
||||
position: absolute;
|
||||
top: -0.08em;
|
||||
right: 100%;
|
||||
margin-right: 0.48em;
|
||||
color: var(--stats-accent);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="heading-link"]:hover [data-slot="heading-anchor"],
|
||||
[data-page="stats"] [data-slot="heading-link"]:focus-visible [data-slot="heading-anchor"] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="leaderboard"],
|
||||
[data-page="stats"] [data-slot="leaderboard-featured"],
|
||||
[data-page="stats"] [data-slot="leaderboard-compact"],
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import { useI18n } from "../context/i18n"
|
|||
import { useLanguage } from "../context/language"
|
||||
import { localizedUrl } from "../lib/language"
|
||||
import { findModelCatalogEntry, getModelCatalog, type ModelCatalog } from "./model-catalog"
|
||||
import { SectionHeading } from "./section-heading"
|
||||
import { setStatsPageCacheHeaders } from "./stats-cache"
|
||||
import {
|
||||
applyThemePreference,
|
||||
|
|
@ -273,7 +274,11 @@ function Hero(props: { updatedAt: string | null }) {
|
|||
</p>
|
||||
<div data-slot="hero-canvas">
|
||||
<div data-slot="hero-pattern" aria-hidden="true" />
|
||||
<h1>{i18n.t("footer.modelData")}</h1>
|
||||
<h1>
|
||||
<a data-slot="heading-link" href="#overview">
|
||||
{i18n.t("footer.modelData")}
|
||||
</a>
|
||||
</h1>
|
||||
<p data-slot="hero-copy">{i18n.t("home.heroCopy")}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -296,7 +301,7 @@ function StatsLoading() {
|
|||
return (
|
||||
<>
|
||||
<Hero updatedAt={null} />
|
||||
<ChartSection title={i18n.t("home.usageTitle")}>
|
||||
<ChartSection id="top-models" title={i18n.t("home.usageTitle")}>
|
||||
<EmptyState title={i18n.t("home.loadingTitle")} description={i18n.t("home.loadingDescription")} />
|
||||
</ChartSection>
|
||||
</>
|
||||
|
|
@ -314,7 +319,15 @@ function ChartSection(props: {
|
|||
<section id={props.id} data-section="chart">
|
||||
<div data-slot="section-header">
|
||||
<div>
|
||||
<h2>{props.title}</h2>
|
||||
<h2>
|
||||
<Show when={props.id} fallback={props.title}>
|
||||
{(id) => (
|
||||
<a data-slot="heading-link" href={`#${id()}`}>
|
||||
{props.title}
|
||||
</a>
|
||||
)}
|
||||
</Show>
|
||||
</h2>
|
||||
{props.description && <p>{props.description}</p>}
|
||||
</div>
|
||||
{props.controls}
|
||||
|
|
@ -324,12 +337,8 @@ function ChartSection(props: {
|
|||
)
|
||||
}
|
||||
|
||||
function SectionTitle(props: { title: string; description: string }) {
|
||||
return (
|
||||
<p data-slot="section-title">
|
||||
<strong>{props.title}.</strong> <span>{props.description}</span>
|
||||
</p>
|
||||
)
|
||||
function SectionTitle(props: { id: string; title: string; description: string }) {
|
||||
return <SectionHeading href={`#${props.id}`} title={props.title} description={props.description} />
|
||||
}
|
||||
|
||||
function SectionBridge(props: { label: string; href: string }) {
|
||||
|
|
@ -405,9 +414,13 @@ function TopModelsSection(props: { data: StatsHomeData["usage"]; leaderboard: St
|
|||
|
||||
return (
|
||||
<section id="top-models" data-section="top-models">
|
||||
<h2 data-slot="top-models-title">
|
||||
<strong>{i18n.t("nav.topModels")}.</strong> <span>{i18n.t("home.topModelsDescription")}</span>
|
||||
</h2>
|
||||
<SectionHeading
|
||||
as="h2"
|
||||
slot="top-models-title"
|
||||
href="#top-models"
|
||||
title={i18n.t("nav.topModels")}
|
||||
description={i18n.t("home.topModelsDescription")}
|
||||
/>
|
||||
<Show
|
||||
when={data().some((item) => usageTotal(item) > 0)}
|
||||
fallback={<EmptyState title={i18n.t("home.noUsageTitle")} description={i18n.t("home.noUsageDescription")} />}
|
||||
|
|
@ -802,7 +815,11 @@ function UniqueUsersSection(props: { data: StatsHomeData["users"] }) {
|
|||
return (
|
||||
<section id="unique-users" data-section="unique-users">
|
||||
<SectionBridge label={i18n.t("nav.topModels").toUpperCase()} href="#top-models" />
|
||||
<SectionTitle title={i18n.t("home.uniqueUsersTitle")} description={i18n.t("home.uniqueUsersDescription")} />
|
||||
<SectionTitle
|
||||
id="unique-users"
|
||||
title={i18n.t("home.uniqueUsersTitle")}
|
||||
description={i18n.t("home.uniqueUsersDescription")}
|
||||
/>
|
||||
<Show
|
||||
when={data().some((item) => usageTotal(item) > 0)}
|
||||
fallback={
|
||||
|
|
@ -1073,7 +1090,11 @@ function MarketShareSection(props: { data: StatsHomeData["market"] }) {
|
|||
}}
|
||||
>
|
||||
<SectionBridge label={i18n.t("nav.cacheRatio").toUpperCase()} href="#cache-ratio" />
|
||||
<SectionTitle title={i18n.t("home.marketShareTitle")} description={i18n.t("home.marketShareDescription")} />
|
||||
<SectionTitle
|
||||
id="market-share"
|
||||
title={i18n.t("home.marketShareTitle")}
|
||||
description={i18n.t("home.marketShareDescription")}
|
||||
/>
|
||||
<Show
|
||||
when={activeDay()}
|
||||
fallback={<EmptyState title={i18n.t("home.noMarketTitle")} description={i18n.t("home.noMarketDescription")} />}
|
||||
|
|
@ -1298,7 +1319,7 @@ function GeoBreakdownSection(props: { data: StatsHomeData["country"] }) {
|
|||
}}
|
||||
>
|
||||
<SectionBridge label={i18n.t("nav.marketShare").toUpperCase()} href="#market-share" />
|
||||
<SectionTitle title={i18n.t("home.geoTitle")} description={i18n.t("home.geoDescription")} />
|
||||
<SectionTitle id="geo-breakdown" title={i18n.t("home.geoTitle")} description={i18n.t("home.geoDescription")} />
|
||||
<Show
|
||||
when={data().length > 0}
|
||||
fallback={<EmptyState title={i18n.t("home.noGeoTitle")} description={i18n.t("home.noGeoDescription")} />}
|
||||
|
|
@ -1583,7 +1604,11 @@ function TokenCostSection(props: { data: StatsHomeData["tokenCost"]; catalog: Mo
|
|||
return (
|
||||
<section id="token-cost" data-section="token-cost">
|
||||
<SectionBridge label={i18n.t("nav.sessionCost").toUpperCase()} href="#session-cost" />
|
||||
<SectionTitle title={i18n.t("home.tokenCostTitle")} description={i18n.t("home.tokenCostDescription")} />
|
||||
<SectionTitle
|
||||
id="token-cost"
|
||||
title={i18n.t("home.tokenCostTitle")}
|
||||
description={i18n.t("home.tokenCostDescription")}
|
||||
/>
|
||||
<Show
|
||||
when={visible().length > 0}
|
||||
fallback={
|
||||
|
|
@ -1666,7 +1691,11 @@ function CacheRatioSection(props: { data: StatsHomeData["cacheRatio"] }) {
|
|||
return (
|
||||
<section id="cache-ratio" data-section="cache-ratio">
|
||||
<SectionBridge label={i18n.t("nav.tokenCost").toUpperCase()} href="#token-cost" />
|
||||
<SectionTitle title={i18n.t("home.cacheRatioTitle")} description={i18n.t("home.cacheRatioDescription")} />
|
||||
<SectionTitle
|
||||
id="cache-ratio"
|
||||
title={i18n.t("home.cacheRatioTitle")}
|
||||
description={i18n.t("home.cacheRatioDescription")}
|
||||
/>
|
||||
<Show
|
||||
when={visible().length > 0}
|
||||
fallback={<EmptyState title={i18n.t("home.noCacheTitle")} description={i18n.t("home.noCacheDescription")} />}
|
||||
|
|
@ -1792,7 +1821,11 @@ function SessionCostSection(props: { data: StatsHomeData["sessionCost"] }) {
|
|||
return (
|
||||
<section id="session-cost" data-section="session-cost">
|
||||
<SectionBridge label={i18n.t("nav.topModels").toUpperCase()} href="#top-models" />
|
||||
<SectionTitle title={i18n.t("home.sessionCostTitle")} description={i18n.t("home.sessionCostDescription")} />
|
||||
<SectionTitle
|
||||
id="session-cost"
|
||||
title={i18n.t("home.sessionCostTitle")}
|
||||
description={i18n.t("home.sessionCostDescription")}
|
||||
/>
|
||||
<Show
|
||||
when={visible().length > 0}
|
||||
fallback={
|
||||
|
|
|
|||
21
packages/stats/app/src/routes/section-heading.tsx
Normal file
21
packages/stats/app/src/routes/section-heading.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
export function SectionHeading(props: {
|
||||
href: string
|
||||
title: string
|
||||
description: string
|
||||
as?: "h2" | "p"
|
||||
slot?: string
|
||||
}) {
|
||||
const content = (
|
||||
<>
|
||||
<strong>
|
||||
<a data-slot="heading-link" href={props.href}>
|
||||
<span data-slot="heading-anchor" aria-hidden="true">#</span>{props.title}.
|
||||
</a>
|
||||
</strong>{" "}
|
||||
<span>{props.description}</span>
|
||||
</>
|
||||
)
|
||||
|
||||
if (props.as === "h2") return <h2 data-slot={props.slot ?? "section-title"}>{content}</h2>
|
||||
return <p data-slot={props.slot ?? "section-title"}>{content}</p>
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue