mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-10 00:28:29 +00:00
feat(data): improved model usage section
This commit is contained in:
parent
203885d241
commit
c3b9c9d888
3 changed files with 554 additions and 3 deletions
|
|
@ -210,7 +210,7 @@ const en = {
|
|||
"model.totalModels": "{{count}} models",
|
||||
"model.momentum": "Momentum",
|
||||
"model.vsPreviousWindow": "vs previous window",
|
||||
"model.usageDescription": "Daily OpenCode Go token volume over the recent two-month window.",
|
||||
"model.usageDescription": "Daily token volume.",
|
||||
"model.noUsageTitle": "No usage",
|
||||
"model.noUsageDescription": "No usage landed in the current window.",
|
||||
"model.dailyTokenChart": "Daily token usage chart",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import {
|
|||
type UsageRange,
|
||||
} from "@opencode-ai/stats-core/domain/home"
|
||||
import { createAsync, query, useParams } from "@solidjs/router"
|
||||
import { createMemo, createSignal, For, onMount, Show, type JSX } from "solid-js"
|
||||
import { createMemo, createSignal, createUniqueId, For, onMount, Show, type JSX } from "solid-js"
|
||||
import { getRequestEvent } from "solid-js/web"
|
||||
import type { FeatureCollection, GeometryObject, GeoJsonProperties } from "geojson"
|
||||
import type { GeometryCollection, Topology } from "topojson-specification"
|
||||
|
|
@ -121,6 +121,7 @@ export default function StatsModel() {
|
|||
const modelHeaderLinks = createMemo<readonly HeaderLink[]>(() => [
|
||||
{ href: "#overview", label: i18n.t("nav.overview") },
|
||||
{ href: "#momentum", label: i18n.t("model.momentum") },
|
||||
{ href: "#usage", label: i18n.t("nav.usage") },
|
||||
{ href: "#efficiency", label: i18n.t("nav.efficiency") },
|
||||
{ href: "#geo-breakdown", label: i18n.t("nav.geoBreakdown") },
|
||||
{ href: "#peers", label: i18n.t("nav.peers") },
|
||||
|
|
@ -183,6 +184,7 @@ export default function StatsModel() {
|
|||
/>
|
||||
<ModelOverview catalog={catalogEntry() ?? null} />
|
||||
<ModelMomentumSection data={stats() ?? null} />
|
||||
<ModelUsageSection data={stats() ?? null} />
|
||||
<ModelEfficiencySection data={stats() ?? null} catalog={catalogEntry() ?? null} />
|
||||
<ModelGeoBreakdownSection data={stats()?.country ?? emptyCountryRecord()} />
|
||||
<ModelPeersSection data={stats() ?? null} />
|
||||
|
|
@ -561,6 +563,211 @@ function MomentumMetric(props: { label: string; value: string; watermark?: strin
|
|||
)
|
||||
}
|
||||
|
||||
function ModelUsageSection(props: { data: StatsModelData | null }) {
|
||||
const i18n = useI18n()
|
||||
const activeLineClipId = createUniqueId()
|
||||
const activeLineMaskId = createUniqueId()
|
||||
const [activeIndex, setActiveIndex] = createSignal<number>()
|
||||
const usage = createMemo(() => props.data?.usage ?? [])
|
||||
const tokenMax = createMemo(() => Math.max(0, ...usage().map((item) => item.tokens)) || 1)
|
||||
const linePoints = createMemo(() =>
|
||||
usage().map((point, index) => ({
|
||||
point,
|
||||
x: modelUsagePointX(index, usage().length),
|
||||
y: modelUsageLineY(point.tokens, tokenMax()),
|
||||
})),
|
||||
)
|
||||
const tokenLinePath = createMemo(() => modelUsageLinePath(linePoints()))
|
||||
const activeLineBreak = createMemo(() => {
|
||||
const index = activeIndex()
|
||||
if (index === undefined || linePoints().length < 2) return undefined
|
||||
return modelUsageColumnBounds(index, linePoints().length)
|
||||
})
|
||||
const activeLineClip = createMemo(() => {
|
||||
const index = activeIndex()
|
||||
if (index === undefined || linePoints().length < 2) return undefined
|
||||
return modelUsageColumnInnerBounds(index, linePoints().length)
|
||||
})
|
||||
const activeTooltip = createMemo(() => {
|
||||
const index = activeIndex()
|
||||
const point = index === undefined ? undefined : usage()[index]
|
||||
if (index === undefined || !point) return undefined
|
||||
const bounds = modelUsageColumnBounds(index, usage().length)
|
||||
return {
|
||||
bounds,
|
||||
index,
|
||||
point,
|
||||
tokenY: linePoints()[index]?.y ?? 100,
|
||||
tooltipY: ((linePoints()[index]?.y ?? 100) * 370) / 450,
|
||||
}
|
||||
})
|
||||
const monthTicks = createMemo(() => modelUsageMonthTicks(usage(), props.data?.updatedAt ?? null))
|
||||
|
||||
return (
|
||||
<section id="usage" data-section="model-panel">
|
||||
<SectionTitle href="#usage" title={i18n.t("nav.usage")} description={i18n.t("model.usageDescription")} />
|
||||
<Show
|
||||
when={usage().some((item) => item.tokens > 0)}
|
||||
fallback={
|
||||
<ModelEmptyState title={i18n.t("model.noUsageTitle")} description={i18n.t("model.noUsageDescription")} />
|
||||
}
|
||||
>
|
||||
<div
|
||||
data-component="model-usage-chart"
|
||||
data-variant="model-trend"
|
||||
role="img"
|
||||
aria-label={i18n.t("model.dailyTokenChart")}
|
||||
style={{ "--model-usage-count": usage().length } as JSX.CSSProperties}
|
||||
onPointerLeave={(event) => {
|
||||
if (event.pointerType === "touch") return
|
||||
setActiveIndex(undefined)
|
||||
}}
|
||||
>
|
||||
<div data-slot="model-trend-plot">
|
||||
<Show when={tokenLinePath()}>
|
||||
{(path) => (
|
||||
<>
|
||||
<svg
|
||||
data-slot="model-trend-line"
|
||||
data-layer="base"
|
||||
viewBox="0 0 100 100"
|
||||
preserveAspectRatio="none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Show when={activeLineBreak()} fallback={<path data-slot="model-trend-line-base" d={path()} />}>
|
||||
{(lineBreak) => (
|
||||
<>
|
||||
<defs>
|
||||
<mask id={activeLineMaskId} maskUnits="userSpaceOnUse">
|
||||
<rect x="0" y="-2" width="100" height="104" fill="white" />
|
||||
<rect x={lineBreak().x} y="-2" width={lineBreak().width} height="104" fill="black" />
|
||||
</mask>
|
||||
</defs>
|
||||
<path data-slot="model-trend-line-base" d={path()} mask={`url(#${activeLineMaskId})`} />
|
||||
</>
|
||||
)}
|
||||
</Show>
|
||||
</svg>
|
||||
<Show when={activeLineClip()}>
|
||||
{(clip) => (
|
||||
<svg
|
||||
data-slot="model-trend-line"
|
||||
data-layer="active"
|
||||
viewBox="0 0 100 100"
|
||||
preserveAspectRatio="none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<defs>
|
||||
<clipPath id={activeLineClipId} clipPathUnits="userSpaceOnUse">
|
||||
<rect x={clip().x} y="-2" width={clip().width} height="104" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<path data-slot="model-trend-line-active" d={path()} clip-path={`url(#${activeLineClipId})`} />
|
||||
</svg>
|
||||
)}
|
||||
</Show>
|
||||
</>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={linePoints().at(-1)}>
|
||||
{(point) => (
|
||||
<span
|
||||
data-slot="model-trend-end-marker"
|
||||
aria-hidden="true"
|
||||
style={
|
||||
{
|
||||
"--model-trend-end-x": `${point().x}%`,
|
||||
"--model-trend-end-top": `${(point().y * 370) / 450}%`,
|
||||
} as JSX.CSSProperties
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
<div data-slot="model-trend-bars">
|
||||
<For each={usage()}>
|
||||
{(point, index) => (
|
||||
<div
|
||||
data-slot="model-trend-column"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={`${point.date} ${formatTokens(point.tokens)} ${i18n.t("lab.tokens")}`}
|
||||
data-active={activeIndex() === index() ? "true" : undefined}
|
||||
data-muted={activeIndex() !== undefined && activeIndex() !== index() ? "true" : undefined}
|
||||
style={
|
||||
{
|
||||
"--model-trend-token-height": `${modelUsageStripHeight(point.tokens, tokenMax())}px`,
|
||||
} as JSX.CSSProperties
|
||||
}
|
||||
onPointerDown={(event) => {
|
||||
if (event.pointerType !== "touch") return
|
||||
setActiveIndex(index())
|
||||
}}
|
||||
onPointerEnter={() => setActiveIndex(index())}
|
||||
onPointerMove={(event) => {
|
||||
if (event.pointerType === "touch") return
|
||||
setActiveIndex(index())
|
||||
}}
|
||||
onClick={() => setActiveIndex(index())}
|
||||
onFocus={() => setActiveIndex(index())}
|
||||
onBlur={() => setActiveIndex(undefined)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key !== "Enter" && event.key !== " ") return
|
||||
event.preventDefault()
|
||||
setActiveIndex(index())
|
||||
}}
|
||||
>
|
||||
<div data-slot="model-trend-token-bar" />
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<Show when={activeTooltip()} keyed>
|
||||
{(active) => (
|
||||
<div
|
||||
data-component="chart-tooltip"
|
||||
data-placement={active.index > usage().length * 0.62 ? "left" : "right"}
|
||||
style={
|
||||
{
|
||||
"--model-trend-tooltip-left": `${active.bounds.x}%`,
|
||||
"--model-trend-tooltip-right": `${active.bounds.x + active.bounds.width}%`,
|
||||
"--model-trend-token-y": `${active.tokenY}`,
|
||||
"--model-trend-tooltip-y": `${active.tooltipY}`,
|
||||
} as JSX.CSSProperties
|
||||
}
|
||||
>
|
||||
<strong>{formatModelUsageTooltipDate(active.point.date, props.data?.updatedAt ?? null)}</strong>
|
||||
<span>
|
||||
{formatTokens(active.point.tokens)} {i18n.t("lab.tokens")}
|
||||
</span>
|
||||
<div data-slot="tooltip-divider" />
|
||||
<p>
|
||||
<span data-slot="tooltip-label">
|
||||
<i data-kind="tokens" /> {i18n.t("lab.dailyTokens")}
|
||||
</span>
|
||||
<b>{formatTokens(active.point.tokens)}</b>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
<div data-slot="model-trend-months" aria-hidden="true">
|
||||
<For each={monthTicks()}>
|
||||
{(tick) => (
|
||||
<span
|
||||
data-align={tick.align}
|
||||
style={{ "--model-trend-month-left": `${tick.left}%` } as JSX.CSSProperties}
|
||||
>
|
||||
{tick.label}
|
||||
</span>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function ModelEfficiencySection(props: { data: StatsModelData | null; catalog: ModelCatalogEntry | null }) {
|
||||
const i18n = useI18n()
|
||||
return (
|
||||
|
|
@ -1014,6 +1221,93 @@ function formatMomentumDateLabel(date: string) {
|
|||
return `${shortMonths[parsed.getUTCMonth()]} ${parsed.getUTCDate()}`
|
||||
}
|
||||
|
||||
function modelUsageStripHeight(value: number, max: number) {
|
||||
if (value <= 0 || max <= 0) return 0
|
||||
return Math.max(2, (value / max) * 40)
|
||||
}
|
||||
|
||||
function modelUsageLineY(value: number, max: number) {
|
||||
if (value <= 0 || max <= 0) return 100
|
||||
return Math.max(0, 100 - (value / max) * 100)
|
||||
}
|
||||
|
||||
function modelUsagePointX(index: number, count: number) {
|
||||
if (count <= 1) return 50
|
||||
return ((index + 0.5) / count) * 100
|
||||
}
|
||||
|
||||
function modelUsageColumnBounds(index: number, count: number) {
|
||||
if (count <= 0) return { x: 0, width: 100 }
|
||||
return { x: (index / count) * 100, width: 100 / count }
|
||||
}
|
||||
|
||||
function modelUsageColumnInnerBounds(index: number, count: number) {
|
||||
const bounds = modelUsageColumnBounds(index, count)
|
||||
const inset = Math.min(bounds.width * 0.1, 0.24)
|
||||
return { x: bounds.x + inset, width: Math.max(0.001, bounds.width - inset * 2) }
|
||||
}
|
||||
|
||||
type ModelUsageLinePoint = { x: number; y: number }
|
||||
|
||||
function modelUsageLinePath(points: ModelUsageLinePoint[]) {
|
||||
if (points.length === 0) return ""
|
||||
if (points.length === 1) return `M ${formatUsagePathNumber(points[0].x)} ${formatUsagePathNumber(points[0].y)}`
|
||||
|
||||
return points.slice(0, -1).reduce(
|
||||
(path, point, index) => {
|
||||
const next = points[index + 1]
|
||||
const previous = points[index - 1] ?? point
|
||||
const afterNext = points[index + 2] ?? next
|
||||
const controlA = {
|
||||
x: point.x + (next.x - previous.x) / 6,
|
||||
y: clampUsagePercent(point.y + (next.y - previous.y) / 6),
|
||||
}
|
||||
const controlB = {
|
||||
x: next.x - (afterNext.x - point.x) / 6,
|
||||
y: clampUsagePercent(next.y - (afterNext.y - point.y) / 6),
|
||||
}
|
||||
return `${path} C ${formatUsagePathNumber(controlA.x)} ${formatUsagePathNumber(controlA.y)}, ${formatUsagePathNumber(controlB.x)} ${formatUsagePathNumber(controlB.y)}, ${formatUsagePathNumber(next.x)} ${formatUsagePathNumber(next.y)}`
|
||||
},
|
||||
`M ${formatUsagePathNumber(points[0].x)} ${formatUsagePathNumber(points[0].y)}`,
|
||||
)
|
||||
}
|
||||
|
||||
function formatUsagePathNumber(value: number) {
|
||||
return Number(value.toFixed(3))
|
||||
}
|
||||
|
||||
function clampUsagePercent(value: number) {
|
||||
return Math.max(0, Math.min(100, value))
|
||||
}
|
||||
|
||||
function modelUsageMonthTicks(points: ModelUsagePoint[], updatedAt: string | null) {
|
||||
const seen = new Set<string>()
|
||||
return points.flatMap((point, index) => {
|
||||
const parsed = parseMomentumDate(point.date, updatedAt)
|
||||
const label = shortMonths[parsed.getUTCMonth()]
|
||||
if (!label || seen.has(label)) return []
|
||||
seen.add(label)
|
||||
return [
|
||||
{
|
||||
label,
|
||||
left: modelUsagePointX(index, points.length),
|
||||
align: index === 0 ? "start" : index >= points.length - 2 ? "end" : "center",
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
function formatModelUsageTooltipDate(value: string, updatedAt: string | null) {
|
||||
const date = parseMomentumDate(value, updatedAt)
|
||||
if (date.getUTCFullYear() === 1970) return value
|
||||
return new Intl.DateTimeFormat("en", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
timeZone: "UTC",
|
||||
}).format(date)
|
||||
}
|
||||
|
||||
function formatRankLabel(rank: number | null) {
|
||||
if (rank === null) return "--"
|
||||
return `#${String(rank).padStart(2, "0")}`
|
||||
|
|
|
|||
|
|
@ -4871,6 +4871,200 @@
|
|||
background: var(--lab-usage-line);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] {
|
||||
--model-trend-gap: 4px;
|
||||
--model-trend-column-gutter: calc(var(--model-trend-gap) / 2);
|
||||
--model-trend-line-bottom: 80px;
|
||||
--model-trend-bar: #dbdbdb;
|
||||
--model-trend-line: #808080;
|
||||
--model-trend-active: #3b5cf6;
|
||||
--model-trend-band: #f2f2f2;
|
||||
grid-template-rows: minmax(0, 450px) 14px;
|
||||
gap: 40px;
|
||||
height: 504px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-plot"] {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-plot"]::after {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
background: linear-gradient(to bottom, transparent 42%, color-mix(in srgb, var(--stats-text) 3%, transparent));
|
||||
content: "";
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-line"] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: auto;
|
||||
left: 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: calc(100% - var(--model-trend-line-bottom));
|
||||
overflow: visible;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-line"][data-layer="base"] {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-line"][data-layer="active"] {
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-line"] path {
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: 1.5;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-line-base"] {
|
||||
stroke: var(--model-trend-line);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-line-active"] {
|
||||
stroke: var(--model-trend-active);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-end-marker"] {
|
||||
position: absolute;
|
||||
top: var(--model-trend-end-top);
|
||||
left: var(--model-trend-end-x);
|
||||
z-index: 3;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: var(--model-trend-bar);
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-bars"] {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
gap: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-column"] {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-column"]::before {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
background: transparent;
|
||||
content: "";
|
||||
pointer-events: none;
|
||||
transition: background 120ms ease;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-column"][data-active="true"]::before,
|
||||
[data-page="stats"] [data-slot="model-trend-column"]:focus-visible::before {
|
||||
background: var(--model-trend-band);
|
||||
box-shadow:
|
||||
inset var(--model-trend-column-gutter) 0 0 var(--stats-bg),
|
||||
inset calc(var(--model-trend-column-gutter) * -1) 0 0 var(--stats-bg);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-token-bar"] {
|
||||
position: absolute;
|
||||
right: var(--model-trend-column-gutter);
|
||||
bottom: 0;
|
||||
left: var(--model-trend-column-gutter);
|
||||
z-index: 2;
|
||||
height: var(--model-trend-token-height);
|
||||
background: var(--model-trend-bar);
|
||||
transition:
|
||||
background 120ms ease,
|
||||
opacity 120ms ease;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-column"][data-active="true"] [data-slot="model-trend-token-bar"] {
|
||||
background: var(--model-trend-active);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-column"][data-muted="true"] [data-slot="model-trend-token-bar"] {
|
||||
opacity: 0.64;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-months"] {
|
||||
position: relative;
|
||||
height: 14px;
|
||||
color: var(--stats-muted);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-months"] span {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: var(--model-trend-month-left);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-months"] span[data-align="start"] {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-slot="model-trend-months"] span[data-align="end"] {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] [data-component="chart-tooltip"] {
|
||||
z-index: 6;
|
||||
top: clamp(12px, calc(var(--model-trend-tooltip-y) * 1% - 46px), calc(100% - 108px));
|
||||
width: 192px;
|
||||
min-width: 192px;
|
||||
background: var(--stats-bg);
|
||||
}
|
||||
|
||||
[data-page="stats"]
|
||||
[data-component="model-usage-chart"][data-variant="model-trend"]
|
||||
[data-component="chart-tooltip"][data-placement="right"] {
|
||||
right: auto;
|
||||
left: calc(var(--model-trend-tooltip-right) + 8px);
|
||||
}
|
||||
|
||||
[data-page="stats"]
|
||||
[data-component="model-usage-chart"][data-variant="model-trend"]
|
||||
[data-component="chart-tooltip"][data-placement="left"] {
|
||||
right: calc(100% - var(--model-trend-tooltip-left) + 8px);
|
||||
left: auto;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] [data-component="chart-tooltip"] p {
|
||||
height: 16px;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
[data-page="stats"]
|
||||
[data-component="model-usage-chart"][data-variant="model-trend"]
|
||||
[data-component="chart-tooltip"]
|
||||
i[data-kind="tokens"] {
|
||||
background: var(--model-trend-active);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-peer-list"] {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
|
|
@ -4966,6 +5160,15 @@
|
|||
--lab-usage-band: #242424;
|
||||
}
|
||||
|
||||
[data-page="stats"][data-theme="dark"] [data-component="model-usage-chart"][data-variant="model-trend"],
|
||||
:root[data-stats-theme="dark"]
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-component="model-usage-chart"][data-variant="model-trend"] {
|
||||
--model-trend-bar: #3a3a3a;
|
||||
--model-trend-line: #a3a3a3;
|
||||
--model-trend-band: #242424;
|
||||
}
|
||||
|
||||
[data-page="stats"][data-theme="dark"] [data-component="chart-tooltip"],
|
||||
:root[data-stats-theme="dark"] [data-page="stats"]:not([data-theme="light"]) [data-component="chart-tooltip"] {
|
||||
background: #242424f2;
|
||||
|
|
@ -5131,6 +5334,14 @@
|
|||
--lab-usage-band: #242424;
|
||||
}
|
||||
|
||||
:root:not([data-stats-theme="light"])
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-component="model-usage-chart"][data-variant="model-trend"] {
|
||||
--model-trend-bar: #3a3a3a;
|
||||
--model-trend-line: #a3a3a3;
|
||||
--model-trend-band: #242424;
|
||||
}
|
||||
|
||||
:root:not([data-stats-theme="light"]) [data-page="stats"]:not([data-theme="light"]) [data-component="chart-tooltip"] {
|
||||
background: #242424f2;
|
||||
}
|
||||
|
|
@ -5229,15 +5440,27 @@
|
|||
[data-page="stats"][data-theme="dark"]
|
||||
[data-component="model-usage-chart"][data-variant="lab-usage"]
|
||||
[data-component="chart-tooltip"],
|
||||
[data-page="stats"][data-theme="dark"]
|
||||
[data-component="model-usage-chart"][data-variant="model-trend"]
|
||||
[data-component="chart-tooltip"],
|
||||
:root[data-stats-theme="dark"]
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-component="model-usage-chart"][data-variant="lab-usage"]
|
||||
[data-component="chart-tooltip"],
|
||||
:root[data-stats-theme="dark"]
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-component="model-usage-chart"][data-variant="model-trend"]
|
||||
[data-component="chart-tooltip"],
|
||||
:root:not([data-stats-theme="light"])
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-component="model-usage-chart"][data-variant="lab-usage"]
|
||||
[data-component="chart-tooltip"],
|
||||
[data-page="stats"] [data-component="model-usage-chart"][data-variant="lab-usage"] [data-component="chart-tooltip"] {
|
||||
:root:not([data-stats-theme="light"])
|
||||
[data-page="stats"]:not([data-theme="light"])
|
||||
[data-component="model-usage-chart"][data-variant="model-trend"]
|
||||
[data-component="chart-tooltip"],
|
||||
[data-page="stats"] [data-component="model-usage-chart"][data-variant="lab-usage"] [data-component="chart-tooltip"],
|
||||
[data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] [data-component="chart-tooltip"] {
|
||||
background: var(--stats-bg);
|
||||
}
|
||||
|
||||
|
|
@ -5514,6 +5737,11 @@
|
|||
margin-top: 16px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] [data-component="chart-tooltip"] {
|
||||
position: absolute;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="footer"] {
|
||||
padding: 88px 24px 24px;
|
||||
}
|
||||
|
|
@ -5935,6 +6163,35 @@
|
|||
min-width: 0;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] {
|
||||
--model-usage-mobile-bar-width: 20px;
|
||||
--model-usage-mobile-edge-space: 48px;
|
||||
--model-usage-mobile-track-width: calc(
|
||||
var(--model-usage-count) * var(--model-usage-mobile-bar-width) + var(--model-usage-mobile-edge-space)
|
||||
);
|
||||
--model-trend-line-bottom: 104px;
|
||||
grid-template-rows: minmax(0, 360px) 14px;
|
||||
gap: 24px;
|
||||
height: 398px;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] [data-slot="model-trend-plot"],
|
||||
[data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] [data-slot="model-trend-months"] {
|
||||
direction: ltr;
|
||||
width: var(--model-usage-mobile-track-width);
|
||||
min-width: var(--model-usage-mobile-track-width);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] [data-slot="model-trend-column"] {
|
||||
flex: 0 0 var(--model-usage-mobile-bar-width);
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] [data-component="chart-tooltip"] {
|
||||
top: auto;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
[data-page="stats"] [data-component="model-peer-list"] a {
|
||||
grid-template-columns: 30px minmax(0, 1fr) minmax(64px, auto);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue