Combine top models and leaderboard sections
This commit is contained in:
parent
a4bc76d2a4
commit
c977cb7f31
2 changed files with 212 additions and 112 deletions
|
|
@ -1033,7 +1033,7 @@
|
||||||
grid-template-rows: 34px minmax(0, 1fr);
|
grid-template-rows: 34px minmax(0, 1fr);
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 560px;
|
height: clamp(320px, 44vh, 400px);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-slot="top-models-axis"],
|
[data-page="stats"] [data-slot="top-models-axis"],
|
||||||
|
|
@ -1418,7 +1418,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-section="top-models"] [data-slot="chart-footer"] {
|
[data-page="stats"] [data-section="top-models"] [data-slot="chart-footer"] {
|
||||||
margin-top: 40px;
|
margin-top: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-component="usage-filter"] {
|
[data-page="stats"] [data-component="usage-filter"] {
|
||||||
|
|
@ -1706,27 +1706,15 @@
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-component="leaderboard"],
|
|
||||||
[data-page="stats"] [data-slot="leaderboard-featured"],
|
|
||||||
[data-page="stats"] [data-slot="leaderboard-compact"],
|
|
||||||
[data-page="stats"] [data-slot="leaderboard-column"],
|
|
||||||
[data-page="stats"] [data-slot="leaderboard-mobile"] {
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-page="stats"] [data-component="leaderboard"] {
|
[data-page="stats"] [data-component="leaderboard"] {
|
||||||
gap: 0;
|
display: block;
|
||||||
}
|
width: 100%;
|
||||||
|
|
||||||
[data-page="stats"] [data-slot="leaderboard-featured"],
|
|
||||||
[data-page="stats"] [data-slot="leaderboard-compact"] {
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
|
|
||||||
gap: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-slot="leaderboard-pattern"] {
|
[data-page="stats"] [data-slot="leaderboard-pattern"] {
|
||||||
|
scroll-margin-top: 88px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
margin: 16px 0;
|
margin: 32px 0 16px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--stats-hero-pattern);
|
background: var(--stats-hero-pattern);
|
||||||
mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E");
|
mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E");
|
||||||
|
|
@ -1739,16 +1727,28 @@
|
||||||
-webkit-mask-size: 6px 6px;
|
-webkit-mask-size: 6px 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-slot="leaderboard-column"] {
|
[data-page="stats"] [data-slot="leaderboard-scroll"] {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
width: calc(100% + 80px);
|
||||||
|
margin-inline: -40px;
|
||||||
|
padding: 4px 40px 8px;
|
||||||
|
overflow-x: auto;
|
||||||
|
overscroll-behavior-x: contain;
|
||||||
|
scroll-padding-inline: 40px;
|
||||||
|
scroll-snap-type: x proximity;
|
||||||
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-slot="leaderboard-mobile"] {
|
[data-page="stats"] [data-slot="leaderboard-scroll"]::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-section="leaderboard"] [data-slot="chart-footer"] {
|
[data-page="stats"] [data-slot="leaderboard-scroll"] [data-component="leader-card"] {
|
||||||
margin-top: 32px;
|
flex: 0 0 clamp(240px, 31vw, 360px);
|
||||||
|
width: clamp(240px, 31vw, 360px);
|
||||||
|
scroll-snap-align: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-component="leader-card"] {
|
[data-page="stats"] [data-component="leader-card"] {
|
||||||
|
|
@ -1889,6 +1889,26 @@
|
||||||
color: #b82d35;
|
color: #b82d35;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-page="stats"] [data-component="leader-card"][data-active="true"] {
|
||||||
|
border-color: var(--stats-line-strong);
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 0.5px #00000024,
|
||||||
|
0 6px 16px #0000000d,
|
||||||
|
0 2px 6px #0000000f;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-page="stats"] [data-component="leader-card"][data-active="true"] [data-slot="leader-watermark"] {
|
||||||
|
color: var(--stats-layer-2);
|
||||||
|
opacity: 0.86;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-page="stats"] [data-component="leader-card"][data-active="true"] [data-slot="leader-avatar"] {
|
||||||
|
border-color: var(--stats-line-strong);
|
||||||
|
color: var(--stats-text);
|
||||||
|
}
|
||||||
|
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
[data-page="stats"] [data-component="leader-card"]:hover,
|
[data-page="stats"] [data-component="leader-card"]:hover,
|
||||||
[data-page="stats"] [data-component="leader-card"]:focus-visible {
|
[data-page="stats"] [data-component="leader-card"]:focus-visible {
|
||||||
|
|
@ -2331,8 +2351,12 @@
|
||||||
0 2px 4px #0000003d;
|
0 2px 4px #0000003d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-page="stats"][data-theme="dark"] [data-component="leader-card"][data-active="true"],
|
||||||
[data-page="stats"][data-theme="dark"] [data-component="leader-card"]:hover,
|
[data-page="stats"][data-theme="dark"] [data-component="leader-card"]:hover,
|
||||||
[data-page="stats"][data-theme="dark"] [data-component="leader-card"]:focus-visible,
|
[data-page="stats"][data-theme="dark"] [data-component="leader-card"]:focus-visible,
|
||||||
|
:root[data-stats-theme="dark"]
|
||||||
|
[data-page="stats"]:not([data-theme="light"])
|
||||||
|
[data-component="leader-card"][data-active="true"],
|
||||||
:root[data-stats-theme="dark"] [data-page="stats"]:not([data-theme="light"]) [data-component="leader-card"]:hover,
|
:root[data-stats-theme="dark"] [data-page="stats"]:not([data-theme="light"]) [data-component="leader-card"]:hover,
|
||||||
:root[data-stats-theme="dark"]
|
:root[data-stats-theme="dark"]
|
||||||
[data-page="stats"]:not([data-theme="light"])
|
[data-page="stats"]:not([data-theme="light"])
|
||||||
|
|
@ -2344,8 +2368,13 @@
|
||||||
0 2px 6px #00000052;
|
0 2px 6px #00000052;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-page="stats"][data-theme="dark"] [data-component="leader-card"][data-active="true"] [data-slot="leader-watermark"],
|
||||||
[data-page="stats"][data-theme="dark"] [data-component="leader-card"]:hover [data-slot="leader-watermark"],
|
[data-page="stats"][data-theme="dark"] [data-component="leader-card"]:hover [data-slot="leader-watermark"],
|
||||||
[data-page="stats"][data-theme="dark"] [data-component="leader-card"]:focus-visible [data-slot="leader-watermark"],
|
[data-page="stats"][data-theme="dark"] [data-component="leader-card"]:focus-visible [data-slot="leader-watermark"],
|
||||||
|
:root[data-stats-theme="dark"]
|
||||||
|
[data-page="stats"]:not([data-theme="light"])
|
||||||
|
[data-component="leader-card"][data-active="true"]
|
||||||
|
[data-slot="leader-watermark"],
|
||||||
:root[data-stats-theme="dark"]
|
:root[data-stats-theme="dark"]
|
||||||
[data-page="stats"]:not([data-theme="light"])
|
[data-page="stats"]:not([data-theme="light"])
|
||||||
[data-component="leader-card"]:hover
|
[data-component="leader-card"]:hover
|
||||||
|
|
@ -2455,6 +2484,9 @@
|
||||||
0 2px 4px #0000003d;
|
0 2px 4px #0000003d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root:not([data-stats-theme="light"])
|
||||||
|
[data-page="stats"]:not([data-theme="light"])
|
||||||
|
[data-component="leader-card"][data-active="true"],
|
||||||
:root:not([data-stats-theme="light"])
|
:root:not([data-stats-theme="light"])
|
||||||
[data-page="stats"]:not([data-theme="light"])
|
[data-page="stats"]:not([data-theme="light"])
|
||||||
[data-component="leader-card"]:hover,
|
[data-component="leader-card"]:hover,
|
||||||
|
|
@ -2468,6 +2500,10 @@
|
||||||
0 2px 6px #00000052;
|
0 2px 6px #00000052;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root:not([data-stats-theme="light"])
|
||||||
|
[data-page="stats"]:not([data-theme="light"])
|
||||||
|
[data-component="leader-card"][data-active="true"]
|
||||||
|
[data-slot="leader-watermark"],
|
||||||
:root:not([data-stats-theme="light"])
|
:root:not([data-stats-theme="light"])
|
||||||
[data-page="stats"]:not([data-theme="light"])
|
[data-page="stats"]:not([data-theme="light"])
|
||||||
[data-component="leader-card"]:hover
|
[data-component="leader-card"]:hover
|
||||||
|
|
@ -2560,6 +2596,14 @@
|
||||||
[data-page="stats"] [data-section="session-cost"] {
|
[data-page="stats"] [data-section="session-cost"] {
|
||||||
padding: 64px 32px;
|
padding: 64px 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-page="stats"] [data-slot="leaderboard-scroll"] {
|
||||||
|
width: calc(100% + 64px);
|
||||||
|
margin-inline: -32px;
|
||||||
|
padding-right: 32px;
|
||||||
|
padding-left: 32px;
|
||||||
|
scroll-padding-inline: 32px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 58rem) {
|
@media (max-width: 58rem) {
|
||||||
|
|
@ -2603,9 +2647,9 @@
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-slot="leaderboard-featured"],
|
[data-page="stats"] [data-slot="leaderboard-scroll"] [data-component="leader-card"] {
|
||||||
[data-page="stats"] [data-slot="leaderboard-compact"] {
|
flex-basis: 280px;
|
||||||
grid-template-columns: 1fr;
|
width: 280px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-component="leader-card"][data-size="featured"],
|
[data-page="stats"] [data-component="leader-card"][data-size="featured"],
|
||||||
|
|
@ -2701,19 +2745,62 @@
|
||||||
[data-page="stats"] [data-slot="top-models-mobile-controls"] {
|
[data-page="stats"] [data-slot="top-models-mobile-controls"] {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-bottom: 32px;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-section="top-models"] [data-slot="chart-footer"] {
|
[data-page="stats"] [data-section="top-models"] [data-slot="chart-footer"] {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-page="stats"] [data-section="top-models"] [data-slot="chart-footer"] [data-component="usage-filter"] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-slot="leaderboard-featured"],
|
[data-page="stats"] [data-slot="leaderboard-featured"],
|
||||||
[data-page="stats"] [data-slot="leaderboard-pattern"],
|
|
||||||
[data-page="stats"] [data-slot="leaderboard-compact"] {
|
[data-page="stats"] [data-slot="leaderboard-compact"] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-page="stats"] [data-slot="leaderboard-pattern"] {
|
||||||
|
margin: 24px 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-page="stats"] [data-slot="leaderboard-scroll"] {
|
||||||
|
gap: 12px;
|
||||||
|
width: calc(100% + 48px);
|
||||||
|
margin-inline: -24px;
|
||||||
|
padding-right: 24px;
|
||||||
|
padding-left: 24px;
|
||||||
|
scroll-padding-inline: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-page="stats"] [data-slot="leaderboard-scroll"] [data-component="leader-card"] {
|
||||||
|
flex: 0 0 240px;
|
||||||
|
width: 240px;
|
||||||
|
min-height: 156px;
|
||||||
|
gap: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-page="stats"] [data-slot="leaderboard-scroll"] [data-slot="leader-body"] {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-page="stats"] [data-slot="leaderboard-scroll"] [data-slot="leader-avatar"] {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-page="stats"] [data-slot="leaderboard-scroll"] [data-slot="leader-watermark"] {
|
||||||
|
right: -68px;
|
||||||
|
width: 240px;
|
||||||
|
height: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-slot="leaderboard-mobile"] {
|
[data-page="stats"] [data-slot="leaderboard-mobile"] {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -2761,7 +2848,7 @@
|
||||||
|
|
||||||
[data-page="stats"] [data-component="top-models-chart"] {
|
[data-page="stats"] [data-component="top-models-chart"] {
|
||||||
grid-template-rows: 40px minmax(0, 1fr);
|
grid-template-rows: 40px minmax(0, 1fr);
|
||||||
height: 400px;
|
height: 360px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-component="market-share"] {
|
[data-page="stats"] [data-component="market-share"] {
|
||||||
|
|
|
||||||
|
|
@ -159,8 +159,7 @@ export default function StatsHome() {
|
||||||
{(stats) => (
|
{(stats) => (
|
||||||
<>
|
<>
|
||||||
<Hero updatedAt={stats().updatedAt} />
|
<Hero updatedAt={stats().updatedAt} />
|
||||||
<TopModelsSection data={stats().usage} />
|
<TopModelsSection data={stats().usage} leaderboard={stats().leaderboard} />
|
||||||
<LeaderboardSection data={stats().leaderboard} />
|
|
||||||
<MarketShareSection data={stats().market} />
|
<MarketShareSection data={stats().market} />
|
||||||
<TokenCostSection data={stats().tokenCost} />
|
<TokenCostSection data={stats().tokenCost} />
|
||||||
<SessionCostSection data={stats().sessionCost} />
|
<SessionCostSection data={stats().sessionCost} />
|
||||||
|
|
@ -368,11 +367,13 @@ function formatUpdatedAtLabel(value: { date: string; time: string }) {
|
||||||
return `${value.date}, ${value.time}`
|
return `${value.date}, ${value.time}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function TopModelsSection(props: { data: StatsHomeData["usage"] }) {
|
function TopModelsSection(props: { data: StatsHomeData["usage"]; leaderboard: StatsHomeData["leaderboard"] }) {
|
||||||
const [product, setProduct] = createSignal<UsageProduct>("All Users")
|
const [product, setProduct] = createSignal<UsageProduct>("All Users")
|
||||||
const [range, setRange] = createSignal<UsageRange>("2M")
|
const [range, setRange] = createSignal<UsageRange>("2M")
|
||||||
const [sheet, setSheet] = createSignal<"product" | "range">()
|
const [sheet, setSheet] = createSignal<"product" | "range">()
|
||||||
|
const [activeModel, setActiveModel] = createSignal<string>()
|
||||||
const data = createMemo(() => props.data[product()][range()])
|
const data = createMemo(() => props.data[product()][range()])
|
||||||
|
const leaderboard = createMemo(() => props.leaderboard[product()][range()])
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!sheet()) return
|
if (!sheet()) return
|
||||||
|
|
@ -397,28 +398,42 @@ function TopModelsSection(props: { data: StatsHomeData["usage"] }) {
|
||||||
<h2 data-slot="top-models-title">
|
<h2 data-slot="top-models-title">
|
||||||
<strong>Top models.</strong> <span>Usage of models across OpenCode.</span>
|
<strong>Top models.</strong> <span>Usage of models across OpenCode.</span>
|
||||||
</h2>
|
</h2>
|
||||||
<div data-slot="top-models-mobile-controls">
|
|
||||||
<MobileFilterButton
|
|
||||||
label="Product filter"
|
|
||||||
value={product()}
|
|
||||||
expanded={sheet() === "product"}
|
|
||||||
onClick={() => setSheet(sheet() === "product" ? undefined : "product")}
|
|
||||||
/>
|
|
||||||
<MobileFilterButton
|
|
||||||
label="Date range"
|
|
||||||
value={range()}
|
|
||||||
expanded={sheet() === "range"}
|
|
||||||
onClick={() => setSheet(sheet() === "range" ? undefined : "range")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Show
|
<Show
|
||||||
when={data().some((item) => usageTotal(item) > 0)}
|
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 model_stat rows matched this product and range." />}
|
||||||
>
|
>
|
||||||
<TopModelsChart data={data()} range={range()} />
|
<TopModelsChart
|
||||||
|
data={data()}
|
||||||
|
range={range()}
|
||||||
|
activeModel={activeModel()}
|
||||||
|
onActiveModelChange={setActiveModel}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
<div id="leaderboard" data-slot="leaderboard-pattern" aria-hidden="true" />
|
||||||
|
<Show
|
||||||
|
when={leaderboard().length > 0}
|
||||||
|
fallback={
|
||||||
|
<EmptyState title="No leaderboard data" description="No model_stat rows matched this product and range." />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Leaderboard data={leaderboard()} activeModel={activeModel()} onActiveModelChange={setActiveModel} />
|
||||||
</Show>
|
</Show>
|
||||||
<div data-slot="chart-footer">
|
<div data-slot="chart-footer">
|
||||||
<StatsFilters product={product()} range={range()} onProductSelect={setProduct} onRangeSelect={setRange} />
|
<StatsFilters product={product()} range={range()} onProductSelect={setProduct} onRangeSelect={setRange} />
|
||||||
|
<div data-slot="top-models-mobile-controls">
|
||||||
|
<MobileFilterButton
|
||||||
|
label="Product filter"
|
||||||
|
value={product()}
|
||||||
|
expanded={sheet() === "product"}
|
||||||
|
onClick={() => setSheet(sheet() === "product" ? undefined : "product")}
|
||||||
|
/>
|
||||||
|
<MobileFilterButton
|
||||||
|
label="Date range"
|
||||||
|
value={range()}
|
||||||
|
expanded={sheet() === "range"}
|
||||||
|
onClick={() => setSheet(sheet() === "range" ? undefined : "range")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Show when={sheet()}>
|
<Show when={sheet()}>
|
||||||
{(kind) => (
|
{(kind) => (
|
||||||
|
|
@ -575,9 +590,13 @@ function FilterPills<T extends string>(props: {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function TopModelsChart(props: { data: UsagePoint[]; range: UsageRange }) {
|
function TopModelsChart(props: {
|
||||||
|
data: UsagePoint[]
|
||||||
|
range: UsageRange
|
||||||
|
activeModel: string | undefined
|
||||||
|
onActiveModelChange: (model: string | undefined) => void
|
||||||
|
}) {
|
||||||
const [activeIndex, setActiveIndex] = createSignal<number>()
|
const [activeIndex, setActiveIndex] = createSignal<number>()
|
||||||
const [activeSegment, setActiveSegment] = createSignal<number>()
|
|
||||||
const maxTotal = createMemo(() => getTopModelsMaxTotal(props.data))
|
const maxTotal = createMemo(() => getTopModelsMaxTotal(props.data))
|
||||||
const activePoint = createMemo(() => props.data[activeIndex() ?? -1])
|
const activePoint = createMemo(() => props.data[activeIndex() ?? -1])
|
||||||
|
|
||||||
|
|
@ -622,31 +641,34 @@ function TopModelsChart(props: { data: UsagePoint[]; range: UsageRange }) {
|
||||||
onPointerDown={(event) => {
|
onPointerDown={(event) => {
|
||||||
if (event.pointerType !== "touch") return
|
if (event.pointerType !== "touch") return
|
||||||
setActiveIndex(dayIndex())
|
setActiveIndex(dayIndex())
|
||||||
setActiveSegment(undefined)
|
props.onActiveModelChange(undefined)
|
||||||
}}
|
}}
|
||||||
onPointerEnter={() => {
|
onPointerEnter={() => {
|
||||||
setActiveIndex(dayIndex())
|
setActiveIndex(dayIndex())
|
||||||
setActiveSegment(undefined)
|
props.onActiveModelChange(undefined)
|
||||||
}}
|
}}
|
||||||
onPointerLeave={(event) => {
|
onPointerLeave={(event) => {
|
||||||
if (event.pointerType === "touch") return
|
if (event.pointerType === "touch") return
|
||||||
setActiveIndex(undefined)
|
setActiveIndex(undefined)
|
||||||
setActiveSegment(undefined)
|
props.onActiveModelChange(undefined)
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
setActiveIndex(dayIndex())
|
||||||
|
props.onActiveModelChange(undefined)
|
||||||
}}
|
}}
|
||||||
onClick={() => setActiveIndex(dayIndex())}
|
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
setActiveIndex(dayIndex())
|
setActiveIndex(dayIndex())
|
||||||
setActiveSegment(undefined)
|
props.onActiveModelChange(undefined)
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
setActiveIndex(undefined)
|
setActiveIndex(undefined)
|
||||||
setActiveSegment(undefined)
|
props.onActiveModelChange(undefined)
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
if (event.key !== "Enter" && event.key !== " ") return
|
if (event.key !== "Enter" && event.key !== " ") return
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
setActiveIndex(dayIndex())
|
setActiveIndex(dayIndex())
|
||||||
setActiveSegment(undefined)
|
props.onActiveModelChange(undefined)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div data-slot="top-models-stack" style={{ "grid-template-rows": getTopModelsSegmentRows(day) }}>
|
<div data-slot="top-models-stack" style={{ "grid-template-rows": getTopModelsSegmentRows(day) }}>
|
||||||
|
|
@ -654,28 +676,30 @@ function TopModelsChart(props: { data: UsagePoint[]; range: UsageRange }) {
|
||||||
{(item) => (
|
{(item) => (
|
||||||
<i
|
<i
|
||||||
data-series={item.index}
|
data-series={item.index}
|
||||||
data-active={activeSegment() === item.index ? "true" : undefined}
|
data-model={item.segment.model}
|
||||||
|
data-active={props.activeModel === item.segment.model ? "true" : undefined}
|
||||||
style={{
|
style={{
|
||||||
background: getTopModelsSegmentColor(
|
background: getTopModelsSegmentColor(
|
||||||
|
item.segment.model,
|
||||||
item.index,
|
item.index,
|
||||||
activeIndex() !== undefined && activeIndex() !== dayIndex(),
|
activeIndex() !== undefined && activeIndex() !== dayIndex(),
|
||||||
activeSegment(),
|
props.activeModel,
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
onPointerEnter={(event) => {
|
onPointerEnter={(event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
setActiveIndex(dayIndex())
|
setActiveIndex(dayIndex())
|
||||||
setActiveSegment(item.index)
|
props.onActiveModelChange(item.segment.model)
|
||||||
}}
|
}}
|
||||||
onPointerDown={(event) => {
|
onPointerDown={(event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
setActiveIndex(dayIndex())
|
setActiveIndex(dayIndex())
|
||||||
setActiveSegment(item.index)
|
props.onActiveModelChange(item.segment.model)
|
||||||
}}
|
}}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
setActiveIndex(dayIndex())
|
setActiveIndex(dayIndex())
|
||||||
setActiveSegment(item.index)
|
props.onActiveModelChange(item.segment.model)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
@ -693,9 +717,11 @@ function TopModelsChart(props: { data: UsagePoint[]; range: UsageRange }) {
|
||||||
<For each={visibleTopModelsSegments(point())}>
|
<For each={visibleTopModelsSegments(point())}>
|
||||||
{(item) => (
|
{(item) => (
|
||||||
<p
|
<p
|
||||||
data-active={activeSegment() === item.index ? "true" : undefined}
|
data-active={props.activeModel === item.segment.model ? "true" : undefined}
|
||||||
data-muted={
|
data-muted={
|
||||||
activeSegment() !== undefined && activeSegment() !== item.index ? "true" : undefined
|
props.activeModel !== undefined && props.activeModel !== item.segment.model
|
||||||
|
? "true"
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span data-slot="tooltip-label">
|
<span data-slot="tooltip-label">
|
||||||
|
|
@ -746,9 +772,14 @@ function stackedTopModelsSegments(point: UsagePoint) {
|
||||||
.sort((a, b) => a.segment.value - b.segment.value || a.index - b.index)
|
.sort((a, b) => a.segment.value - b.segment.value || a.index - b.index)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTopModelsSegmentColor(index: number, muted: boolean, activeSegment: number | undefined) {
|
function getTopModelsSegmentColor(
|
||||||
if (activeSegment !== undefined)
|
model: string,
|
||||||
return activeSegment === index ? (usageColors[index] ?? "var(--stats-text)") : "var(--stats-layer-2)"
|
index: number,
|
||||||
|
muted: boolean,
|
||||||
|
activeModel: string | undefined,
|
||||||
|
) {
|
||||||
|
if (activeModel !== undefined)
|
||||||
|
return activeModel === model ? (usageColors[index] ?? "var(--stats-text)") : "var(--stats-layer-2)"
|
||||||
if (muted) return "var(--stats-layer-2)"
|
if (muted) return "var(--stats-layer-2)"
|
||||||
return usageColors[index] ?? "var(--stats-text)"
|
return usageColors[index] ?? "var(--stats-text)"
|
||||||
}
|
}
|
||||||
|
|
@ -781,67 +812,49 @@ function formatTokens(value: number) {
|
||||||
return `${Math.round(value * 1000)}B`
|
return `${Math.round(value * 1000)}B`
|
||||||
}
|
}
|
||||||
|
|
||||||
function LeaderboardSection(props: { data: StatsHomeData["leaderboard"] }) {
|
function Leaderboard(props: {
|
||||||
const [product, setProduct] = createSignal<UsageProduct>("All Users")
|
data: LeaderboardEntry[]
|
||||||
const [range, setRange] = createSignal<UsageRange>("2M")
|
activeModel: string | undefined
|
||||||
const data = createMemo(() => props.data[product()][range()])
|
onActiveModelChange: (model: string | undefined) => void
|
||||||
|
}) {
|
||||||
return (
|
|
||||||
<section id="leaderboard" data-section="leaderboard">
|
|
||||||
<SectionTitle
|
|
||||||
title="Leaderboard"
|
|
||||||
description="Shown are the sum of prompt and completion tokens per model, including reasoning tokens."
|
|
||||||
/>
|
|
||||||
<Show
|
|
||||||
when={data().length > 0}
|
|
||||||
fallback={
|
|
||||||
<EmptyState title="No leaderboard data" description="No model_stat rows matched this product and range." />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Leaderboard data={data()} />
|
|
||||||
</Show>
|
|
||||||
<div data-slot="chart-footer">
|
|
||||||
<StatsFilters product={product()} range={range()} onProductSelect={setProduct} onRangeSelect={setRange} />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Leaderboard(props: { data: LeaderboardEntry[] }) {
|
|
||||||
const featured = createMemo(() => props.data.slice(0, 3))
|
|
||||||
const columns = createMemo(() =>
|
|
||||||
[0, 1, 2].map((index) => props.data.slice(3 + index * 5, 8 + index * 5)).filter((column) => column.length > 0),
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-component="leaderboard" role="list" aria-label="Model token leaderboard">
|
<div data-component="leaderboard" role="list" aria-label="Model token leaderboard">
|
||||||
<div data-slot="leaderboard-featured">
|
<div data-slot="leaderboard-scroll" aria-label="Scrollable model token leaderboard">
|
||||||
<For each={featured()}>{(entry) => <LeaderboardCard entry={entry} size="featured" />}</For>
|
<For each={props.data}>
|
||||||
</div>
|
{(entry) => (
|
||||||
<div data-slot="leaderboard-pattern" aria-hidden="true" />
|
<LeaderboardCard
|
||||||
<div data-slot="leaderboard-compact">
|
entry={entry}
|
||||||
<For each={columns()}>
|
active={props.activeModel === entry.model}
|
||||||
{(column) => (
|
onActiveModelChange={props.onActiveModelChange}
|
||||||
<div data-slot="leaderboard-column">
|
/>
|
||||||
<For each={column}>{(entry) => <LeaderboardCard entry={entry} size="compact" />}</For>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
<div data-slot="leaderboard-mobile" aria-label="Scrollable model token leaderboard">
|
|
||||||
<For each={props.data}>{(entry) => <LeaderboardCard entry={entry} size="featured" />}</For>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function LeaderboardCard(props: { entry: LeaderboardEntry; size: "featured" | "compact" }) {
|
function LeaderboardCard(props: {
|
||||||
|
entry: LeaderboardEntry
|
||||||
|
active: boolean
|
||||||
|
onActiveModelChange: (model: string | undefined) => void
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<article
|
<article
|
||||||
data-component="leader-card"
|
data-component="leader-card"
|
||||||
data-size={props.size}
|
data-size="featured"
|
||||||
|
data-active={props.active ? "true" : undefined}
|
||||||
role="listitem"
|
role="listitem"
|
||||||
|
tabIndex={0}
|
||||||
aria-label={`${String(props.entry.rank).padStart(2, "0")} ${props.entry.model} by ${props.entry.author}`}
|
aria-label={`${String(props.entry.rank).padStart(2, "0")} ${props.entry.model} by ${props.entry.author}`}
|
||||||
|
onPointerEnter={() => props.onActiveModelChange(props.entry.model)}
|
||||||
|
onPointerLeave={(event) => {
|
||||||
|
if (event.pointerType === "touch") return
|
||||||
|
props.onActiveModelChange(undefined)
|
||||||
|
}}
|
||||||
|
onFocus={() => props.onActiveModelChange(props.entry.model)}
|
||||||
|
onBlur={() => props.onActiveModelChange(undefined)}
|
||||||
|
onClick={() => props.onActiveModelChange(props.entry.model)}
|
||||||
>
|
>
|
||||||
<span data-slot="rank">{String(props.entry.rank).padStart(2, "0")}</span>
|
<span data-slot="rank">{String(props.entry.rank).padStart(2, "0")}</span>
|
||||||
<ProviderIcon data-slot="leader-watermark" aria-hidden="true" id={getProviderIconId(props.entry.author)} />
|
<ProviderIcon data-slot="leader-watermark" aria-hidden="true" id={getProviderIconId(props.entry.author)} />
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue