fix(data): refine model trend charts
This commit is contained in:
@@ -214,7 +214,7 @@ const en = {
|
|||||||
"model.noUsageTitle": "No usage",
|
"model.noUsageTitle": "No usage",
|
||||||
"model.noUsageDescription": "No usage landed in the current window.",
|
"model.noUsageDescription": "No usage landed in the current window.",
|
||||||
"model.dailyTokenChart": "Daily token usage chart",
|
"model.dailyTokenChart": "Daily token usage chart",
|
||||||
"model.usersDescription": "Daily unique OpenCode Go users over the recent two-month window.",
|
"model.usersDescription": "Daily unique users.",
|
||||||
"model.noUsersTitle": "No user data",
|
"model.noUsersTitle": "No user data",
|
||||||
"model.noUsersDescription": "No user-bearing rows landed in the current window.",
|
"model.noUsersDescription": "No user-bearing rows landed in the current window.",
|
||||||
"model.dailyUserChart": "Daily unique user chart",
|
"model.dailyUserChart": "Daily unique user chart",
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ export default function StatsModel() {
|
|||||||
{ href: "#overview", label: i18n.t("nav.overview") },
|
{ href: "#overview", label: i18n.t("nav.overview") },
|
||||||
{ href: "#momentum", label: i18n.t("model.momentum") },
|
{ href: "#momentum", label: i18n.t("model.momentum") },
|
||||||
{ href: "#usage", label: i18n.t("nav.usage") },
|
{ href: "#usage", label: i18n.t("nav.usage") },
|
||||||
|
{ href: "#unique-users", label: i18n.t("model.uniqueUsers") },
|
||||||
{ href: "#efficiency", label: i18n.t("nav.efficiency") },
|
{ href: "#efficiency", label: i18n.t("nav.efficiency") },
|
||||||
{ href: "#geo-breakdown", label: i18n.t("nav.geoBreakdown") },
|
{ href: "#geo-breakdown", label: i18n.t("nav.geoBreakdown") },
|
||||||
{ href: "#peers", label: i18n.t("nav.peers") },
|
{ href: "#peers", label: i18n.t("nav.peers") },
|
||||||
@@ -185,6 +186,7 @@ export default function StatsModel() {
|
|||||||
<ModelOverview catalog={catalogEntry() ?? null} />
|
<ModelOverview catalog={catalogEntry() ?? null} />
|
||||||
<ModelMomentumSection data={stats() ?? null} />
|
<ModelMomentumSection data={stats() ?? null} />
|
||||||
<ModelUsageSection data={stats() ?? null} />
|
<ModelUsageSection data={stats() ?? null} />
|
||||||
|
<ModelUniqueUsersSection data={stats() ?? null} />
|
||||||
<ModelEfficiencySection data={stats() ?? null} catalog={catalogEntry() ?? null} />
|
<ModelEfficiencySection data={stats() ?? null} catalog={catalogEntry() ?? null} />
|
||||||
<ModelGeoBreakdownSection data={stats()?.country ?? emptyCountryRecord()} />
|
<ModelGeoBreakdownSection data={stats()?.country ?? emptyCountryRecord()} />
|
||||||
<ModelPeersSection data={stats() ?? null} />
|
<ModelPeersSection data={stats() ?? null} />
|
||||||
@@ -565,19 +567,82 @@ function MomentumMetric(props: { label: string; value: string; watermark?: strin
|
|||||||
|
|
||||||
function ModelUsageSection(props: { data: StatsModelData | null }) {
|
function ModelUsageSection(props: { data: StatsModelData | null }) {
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
|
return (
|
||||||
|
<ModelTrendSection
|
||||||
|
data={props.data}
|
||||||
|
id="usage"
|
||||||
|
title={i18n.t("nav.usage")}
|
||||||
|
description={i18n.t("model.usageDescription")}
|
||||||
|
ariaLabel={i18n.t("model.dailyTokenChart")}
|
||||||
|
emptyTitle={i18n.t("model.noUsageTitle")}
|
||||||
|
emptyDescription={i18n.t("model.noUsageDescription")}
|
||||||
|
value={(point) => point.tokens}
|
||||||
|
formatValue={formatTokens}
|
||||||
|
valueUnit={i18n.t("lab.tokens")}
|
||||||
|
rowLabel={i18n.t("lab.dailyTokens")}
|
||||||
|
lineTone="muted"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ModelUniqueUsersSection(props: { data: StatsModelData | null }) {
|
||||||
|
const i18n = useI18n()
|
||||||
|
return (
|
||||||
|
<ModelTrendSection
|
||||||
|
data={props.data}
|
||||||
|
id="unique-users"
|
||||||
|
title={i18n.t("model.uniqueUsers")}
|
||||||
|
description={i18n.t("model.usersDescription")}
|
||||||
|
ariaLabel={i18n.t("model.dailyUserChart")}
|
||||||
|
emptyTitle={i18n.t("model.noUsersTitle")}
|
||||||
|
emptyDescription={i18n.t("model.noUsersDescription")}
|
||||||
|
value={(point) => point.users}
|
||||||
|
formatValue={formatUsers}
|
||||||
|
valueUnit={i18n.t("format.users")}
|
||||||
|
rowLabel={i18n.t("model.uniqueUsers")}
|
||||||
|
lineTone="active"
|
||||||
|
activeLineBaseTone="muted"
|
||||||
|
highlightBars={false}
|
||||||
|
area
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ModelTrendSection(props: {
|
||||||
|
data: StatsModelData | null
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
ariaLabel: string
|
||||||
|
emptyTitle: string
|
||||||
|
emptyDescription: string
|
||||||
|
value: (point: ModelUsagePoint) => number
|
||||||
|
formatValue: (value: number) => string
|
||||||
|
valueUnit: string
|
||||||
|
rowLabel: string
|
||||||
|
lineTone: "muted" | "active"
|
||||||
|
activeLineBaseTone?: "muted" | "active"
|
||||||
|
highlightBars?: boolean
|
||||||
|
area?: boolean
|
||||||
|
}) {
|
||||||
const activeLineClipId = createUniqueId()
|
const activeLineClipId = createUniqueId()
|
||||||
const activeLineMaskId = createUniqueId()
|
const activeLineMaskId = createUniqueId()
|
||||||
|
const areaGradientId = createUniqueId()
|
||||||
const [activeIndex, setActiveIndex] = createSignal<number>()
|
const [activeIndex, setActiveIndex] = createSignal<number>()
|
||||||
|
const highlightBars = createMemo(() => props.highlightBars ?? true)
|
||||||
|
const lineScale = 326 / 450
|
||||||
|
const areaBottom = 100 / lineScale
|
||||||
const usage = createMemo(() => props.data?.usage ?? [])
|
const usage = createMemo(() => props.data?.usage ?? [])
|
||||||
const tokenMax = createMemo(() => Math.max(0, ...usage().map((item) => item.tokens)) || 1)
|
const valueMax = createMemo(() => Math.max(0, ...usage().map((item) => props.value(item))) || 1)
|
||||||
const linePoints = createMemo(() =>
|
const linePoints = createMemo(() =>
|
||||||
usage().map((point, index) => ({
|
usage().map((point, index) => ({
|
||||||
point,
|
point,
|
||||||
x: modelUsagePointX(index, usage().length),
|
x: modelUsagePointX(index, usage().length),
|
||||||
y: modelUsageLineY(point.tokens, tokenMax()),
|
y: modelUsageLineY(props.value(point), valueMax()),
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
const tokenLinePath = createMemo(() => modelUsageLinePath(linePoints()))
|
const linePath = createMemo(() => modelUsageLinePath(linePoints()))
|
||||||
|
const areaPath = createMemo(() => modelUsageAreaPath(linePoints(), areaBottom))
|
||||||
const activeLineBreak = createMemo(() => {
|
const activeLineBreak = createMemo(() => {
|
||||||
const index = activeIndex()
|
const index = activeIndex()
|
||||||
if (index === undefined || linePoints().length < 2) return undefined
|
if (index === undefined || linePoints().length < 2) return undefined
|
||||||
@@ -597,26 +662,24 @@ function ModelUsageSection(props: { data: StatsModelData | null }) {
|
|||||||
bounds,
|
bounds,
|
||||||
index,
|
index,
|
||||||
point,
|
point,
|
||||||
tokenY: linePoints()[index]?.y ?? 100,
|
tooltipY: (linePoints()[index]?.y ?? 100) * lineScale,
|
||||||
tooltipY: ((linePoints()[index]?.y ?? 100) * 370) / 450,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const monthTicks = createMemo(() => modelUsageMonthTicks(usage(), props.data?.updatedAt ?? null))
|
const monthTicks = createMemo(() => modelUsageMonthTicks(usage(), props.data?.updatedAt ?? null))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="usage" data-section="model-panel">
|
<section id={props.id} data-section="model-panel">
|
||||||
<SectionTitle href="#usage" title={i18n.t("nav.usage")} description={i18n.t("model.usageDescription")} />
|
<SectionTitle href={`#${props.id}`} title={props.title} description={props.description} />
|
||||||
<Show
|
<Show
|
||||||
when={usage().some((item) => item.tokens > 0)}
|
when={usage().some((item) => props.value(item) > 0)}
|
||||||
fallback={
|
fallback={<ModelEmptyState title={props.emptyTitle} description={props.emptyDescription} />}
|
||||||
<ModelEmptyState title={i18n.t("model.noUsageTitle")} description={i18n.t("model.noUsageDescription")} />
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
data-component="model-usage-chart"
|
data-component="model-usage-chart"
|
||||||
data-variant="model-trend"
|
data-variant="model-trend"
|
||||||
|
data-highlight-bars={highlightBars() ? "true" : undefined}
|
||||||
role="img"
|
role="img"
|
||||||
aria-label={i18n.t("model.dailyTokenChart")}
|
aria-label={props.ariaLabel}
|
||||||
style={{ "--model-usage-count": usage().length } as JSX.CSSProperties}
|
style={{ "--model-usage-count": usage().length } as JSX.CSSProperties}
|
||||||
onPointerLeave={(event) => {
|
onPointerLeave={(event) => {
|
||||||
if (event.pointerType === "touch") return
|
if (event.pointerType === "touch") return
|
||||||
@@ -624,9 +687,28 @@ function ModelUsageSection(props: { data: StatsModelData | null }) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div data-slot="model-trend-plot">
|
<div data-slot="model-trend-plot">
|
||||||
<Show when={tokenLinePath()}>
|
<Show when={linePath()}>
|
||||||
{(path) => (
|
{(path) => (
|
||||||
<>
|
<>
|
||||||
|
<Show when={props.area && areaPath()}>
|
||||||
|
{(area) => (
|
||||||
|
<svg
|
||||||
|
data-slot="model-trend-area-layer"
|
||||||
|
viewBox={`0 0 100 ${formatUsagePathNumber(areaBottom)}`}
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id={areaGradientId} x1="0%" y1="0%" x2="0%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="var(--model-trend-active)" stop-opacity="0.1" />
|
||||||
|
<stop offset="66%" stop-color="var(--model-trend-active)" stop-opacity="0.045" />
|
||||||
|
<stop offset="100%" stop-color="var(--model-trend-active)" stop-opacity="0" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path data-slot="model-trend-area" d={area()} fill={`url(#${areaGradientId})`} />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
<svg
|
<svg
|
||||||
data-slot="model-trend-line"
|
data-slot="model-trend-line"
|
||||||
data-layer="base"
|
data-layer="base"
|
||||||
@@ -634,7 +716,10 @@ function ModelUsageSection(props: { data: StatsModelData | null }) {
|
|||||||
preserveAspectRatio="none"
|
preserveAspectRatio="none"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<Show when={activeLineBreak()} fallback={<path data-slot="model-trend-line-base" d={path()} />}>
|
<Show
|
||||||
|
when={activeLineBreak()}
|
||||||
|
fallback={<path data-slot="model-trend-line-base" data-tone={props.lineTone} d={path()} />}
|
||||||
|
>
|
||||||
{(lineBreak) => (
|
{(lineBreak) => (
|
||||||
<>
|
<>
|
||||||
<defs>
|
<defs>
|
||||||
@@ -643,7 +728,12 @@ function ModelUsageSection(props: { data: StatsModelData | null }) {
|
|||||||
<rect x={lineBreak().x} y="-2" width={lineBreak().width} height="104" fill="black" />
|
<rect x={lineBreak().x} y="-2" width={lineBreak().width} height="104" fill="black" />
|
||||||
</mask>
|
</mask>
|
||||||
</defs>
|
</defs>
|
||||||
<path data-slot="model-trend-line-base" d={path()} mask={`url(#${activeLineMaskId})`} />
|
<path
|
||||||
|
data-slot="model-trend-line-base"
|
||||||
|
data-tone={props.activeLineBaseTone ?? props.lineTone}
|
||||||
|
d={path()}
|
||||||
|
mask={`url(#${activeLineMaskId})`}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
@@ -673,11 +763,12 @@ function ModelUsageSection(props: { data: StatsModelData | null }) {
|
|||||||
{(point) => (
|
{(point) => (
|
||||||
<span
|
<span
|
||||||
data-slot="model-trend-end-marker"
|
data-slot="model-trend-end-marker"
|
||||||
|
data-tone={props.lineTone}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
"--model-trend-end-x": `${point().x}%`,
|
"--model-trend-end-x": `${point().x}%`,
|
||||||
"--model-trend-end-top": `${(point().y * 370) / 450}%`,
|
"--model-trend-end-top": `${point().y * lineScale}%`,
|
||||||
} as JSX.CSSProperties
|
} as JSX.CSSProperties
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -690,12 +781,14 @@ function ModelUsageSection(props: { data: StatsModelData | null }) {
|
|||||||
data-slot="model-trend-column"
|
data-slot="model-trend-column"
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
aria-label={`${point.date} ${formatTokens(point.tokens)} ${i18n.t("lab.tokens")}`}
|
aria-label={`${point.date} ${props.formatValue(props.value(point))} ${props.valueUnit}`}
|
||||||
data-active={activeIndex() === index() ? "true" : undefined}
|
data-active={activeIndex() === index() ? "true" : undefined}
|
||||||
data-muted={activeIndex() !== undefined && activeIndex() !== index() ? "true" : undefined}
|
data-muted={
|
||||||
|
highlightBars() && activeIndex() !== undefined && activeIndex() !== index() ? "true" : undefined
|
||||||
|
}
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
"--model-trend-token-height": `${modelUsageStripHeight(point.tokens, tokenMax())}px`,
|
"--model-trend-token-height": modelUsageStripHeight(props.value(point), valueMax()),
|
||||||
} as JSX.CSSProperties
|
} as JSX.CSSProperties
|
||||||
}
|
}
|
||||||
onPointerDown={(event) => {
|
onPointerDown={(event) => {
|
||||||
@@ -716,7 +809,9 @@ function ModelUsageSection(props: { data: StatsModelData | null }) {
|
|||||||
setActiveIndex(index())
|
setActiveIndex(index())
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div data-slot="model-trend-token-bar" />
|
<div data-slot="model-trend-token-band">
|
||||||
|
<div data-slot="model-trend-token-bar" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
@@ -730,21 +825,20 @@ function ModelUsageSection(props: { data: StatsModelData | null }) {
|
|||||||
{
|
{
|
||||||
"--model-trend-tooltip-left": `${active.bounds.x}%`,
|
"--model-trend-tooltip-left": `${active.bounds.x}%`,
|
||||||
"--model-trend-tooltip-right": `${active.bounds.x + active.bounds.width}%`,
|
"--model-trend-tooltip-right": `${active.bounds.x + active.bounds.width}%`,
|
||||||
"--model-trend-token-y": `${active.tokenY}`,
|
|
||||||
"--model-trend-tooltip-y": `${active.tooltipY}`,
|
"--model-trend-tooltip-y": `${active.tooltipY}`,
|
||||||
} as JSX.CSSProperties
|
} as JSX.CSSProperties
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<strong>{formatModelUsageTooltipDate(active.point.date, props.data?.updatedAt ?? null)}</strong>
|
<strong>{formatModelUsageTooltipDate(active.point.date, props.data?.updatedAt ?? null)}</strong>
|
||||||
<span>
|
<span>
|
||||||
{formatTokens(active.point.tokens)} {i18n.t("lab.tokens")}
|
{props.formatValue(props.value(active.point))} {props.valueUnit}
|
||||||
</span>
|
</span>
|
||||||
<div data-slot="tooltip-divider" />
|
<div data-slot="tooltip-divider" />
|
||||||
<p>
|
<p>
|
||||||
<span data-slot="tooltip-label">
|
<span data-slot="tooltip-label">
|
||||||
<i data-kind="tokens" /> {i18n.t("lab.dailyTokens")}
|
<i data-kind={props.lineTone === "active" ? "users" : "tokens"} /> {props.rowLabel}
|
||||||
</span>
|
</span>
|
||||||
<b>{formatTokens(active.point.tokens)}</b>
|
<b>{props.formatValue(props.value(active.point))}</b>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1222,8 +1316,8 @@ function formatMomentumDateLabel(date: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function modelUsageStripHeight(value: number, max: number) {
|
function modelUsageStripHeight(value: number, max: number) {
|
||||||
if (value <= 0 || max <= 0) return 0
|
if (value <= 0 || max <= 0) return "0px"
|
||||||
return Math.max(2, (value / max) * 40)
|
return `max(2px, ${(value / max) * 100}%)`
|
||||||
}
|
}
|
||||||
|
|
||||||
function modelUsageLineY(value: number, max: number) {
|
function modelUsageLineY(value: number, max: number) {
|
||||||
@@ -1272,6 +1366,14 @@ function modelUsageLinePath(points: ModelUsageLinePoint[]) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function modelUsageAreaPath(points: ModelUsageLinePoint[], bottom: number) {
|
||||||
|
const path = modelUsageLinePath(points)
|
||||||
|
if (!path || points.length === 0) return ""
|
||||||
|
const first = points[0]
|
||||||
|
const last = points[points.length - 1]
|
||||||
|
return `${path} L ${formatUsagePathNumber(last.x)} ${formatUsagePathNumber(bottom)} L ${formatUsagePathNumber(first.x)} ${formatUsagePathNumber(bottom)} Z`
|
||||||
|
}
|
||||||
|
|
||||||
function formatUsagePathNumber(value: number) {
|
function formatUsagePathNumber(value: number) {
|
||||||
return Number(value.toFixed(3))
|
return Number(value.toFixed(3))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4874,7 +4874,8 @@
|
|||||||
[data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] {
|
[data-page="stats"] [data-component="model-usage-chart"][data-variant="model-trend"] {
|
||||||
--model-trend-gap: 4px;
|
--model-trend-gap: 4px;
|
||||||
--model-trend-column-gutter: calc(var(--model-trend-gap) / 2);
|
--model-trend-column-gutter: calc(var(--model-trend-gap) / 2);
|
||||||
--model-trend-line-bottom: 80px;
|
--model-trend-bar-height: 76px;
|
||||||
|
--model-trend-line-bottom: 124px;
|
||||||
--model-trend-bar: #dbdbdb;
|
--model-trend-bar: #dbdbdb;
|
||||||
--model-trend-line: #808080;
|
--model-trend-line: #808080;
|
||||||
--model-trend-active: #3b5cf6;
|
--model-trend-active: #3b5cf6;
|
||||||
@@ -4921,6 +4922,21 @@
|
|||||||
z-index: 4;
|
z-index: 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-page="stats"] [data-slot="model-trend-area-layer"] {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: visible;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-page="stats"] [data-slot="model-trend-area-layer"] [data-slot="model-trend-area"] {
|
||||||
|
stroke: none;
|
||||||
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-slot="model-trend-line"] path {
|
[data-page="stats"] [data-slot="model-trend-line"] path {
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke-linecap: round;
|
stroke-linecap: round;
|
||||||
@@ -4933,6 +4949,10 @@
|
|||||||
stroke: var(--model-trend-line);
|
stroke: var(--model-trend-line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-page="stats"] [data-slot="model-trend-line-base"][data-tone="active"] {
|
||||||
|
stroke: var(--model-trend-active);
|
||||||
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-slot="model-trend-line-active"] {
|
[data-page="stats"] [data-slot="model-trend-line-active"] {
|
||||||
stroke: var(--model-trend-active);
|
stroke: var(--model-trend-active);
|
||||||
}
|
}
|
||||||
@@ -4949,6 +4969,10 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-page="stats"] [data-slot="model-trend-end-marker"][data-tone="active"] {
|
||||||
|
background: var(--model-trend-active);
|
||||||
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-slot="model-trend-bars"] {
|
[data-page="stats"] [data-slot="model-trend-bars"] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@@ -4986,12 +5010,20 @@
|
|||||||
inset calc(var(--model-trend-column-gutter) * -1) 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"] {
|
[data-page="stats"] [data-slot="model-trend-token-band"] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: var(--model-trend-column-gutter);
|
right: var(--model-trend-column-gutter);
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: var(--model-trend-column-gutter);
|
left: var(--model-trend-column-gutter);
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
height: var(--model-trend-bar-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-page="stats"] [data-slot="model-trend-token-bar"] {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
height: var(--model-trend-token-height);
|
height: var(--model-trend-token-height);
|
||||||
background: var(--model-trend-bar);
|
background: var(--model-trend-bar);
|
||||||
transition:
|
transition:
|
||||||
@@ -4999,11 +5031,17 @@
|
|||||||
opacity 120ms ease;
|
opacity 120ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-slot="model-trend-column"][data-active="true"] [data-slot="model-trend-token-bar"] {
|
[data-page="stats"]
|
||||||
|
[data-component="model-usage-chart"][data-highlight-bars="true"]
|
||||||
|
[data-slot="model-trend-column"][data-active="true"]
|
||||||
|
[data-slot="model-trend-token-bar"] {
|
||||||
background: var(--model-trend-active);
|
background: var(--model-trend-active);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-slot="model-trend-column"][data-muted="true"] [data-slot="model-trend-token-bar"] {
|
[data-page="stats"]
|
||||||
|
[data-component="model-usage-chart"][data-highlight-bars="true"]
|
||||||
|
[data-slot="model-trend-column"][data-muted="true"]
|
||||||
|
[data-slot="model-trend-token-bar"] {
|
||||||
opacity: 0.64;
|
opacity: 0.64;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5065,6 +5103,13 @@
|
|||||||
background: var(--model-trend-active);
|
background: var(--model-trend-active);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-page="stats"]
|
||||||
|
[data-component="model-usage-chart"][data-variant="model-trend"]
|
||||||
|
[data-component="chart-tooltip"]
|
||||||
|
i[data-kind="users"] {
|
||||||
|
background: var(--model-trend-active);
|
||||||
|
}
|
||||||
|
|
||||||
[data-page="stats"] [data-component="model-peer-list"] {
|
[data-page="stats"] [data-component="model-peer-list"] {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
@@ -6169,6 +6214,7 @@
|
|||||||
--model-usage-mobile-track-width: calc(
|
--model-usage-mobile-track-width: calc(
|
||||||
var(--model-usage-count) * var(--model-usage-mobile-bar-width) + var(--model-usage-mobile-edge-space)
|
var(--model-usage-count) * var(--model-usage-mobile-bar-width) + var(--model-usage-mobile-edge-space)
|
||||||
);
|
);
|
||||||
|
--model-trend-bar-height: 64px;
|
||||||
--model-trend-line-bottom: 104px;
|
--model-trend-line-bottom: 104px;
|
||||||
grid-template-rows: minmax(0, 360px) 14px;
|
grid-template-rows: minmax(0, 360px) 14px;
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
|
|||||||
Reference in New Issue
Block a user