feat(console): compare Go and Go Plus limits side by side (#51865)

This commit is contained in:
Victor Navarro 2026-09-28 15:25:55 +02:00 committed by GitHub
parent f416138844
commit b76bbb3692
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 118 additions and 140 deletions

View file

@ -61,12 +61,23 @@
color: var(--go-console-muted);
line-height: 1.25rem;
}
[data-slot="tier-switch"] {
[data-slot="legend"] {
display: flex;
align-items: center;
gap: 0.25rem;
gap: 1rem;
margin: 0;
padding: 0 0 0.375rem;
list-style: none;
color: var(--go-console-secondary);
font-weight: 530;
white-space: nowrap;
}
[data-slot="tier-switch"] i {
[data-slot="legend"] li {
display: flex;
align-items: center;
gap: 0.5rem;
}
[data-slot="legend"] i {
width: 3px;
height: 12px;
border-radius: 1px;
@ -90,27 +101,6 @@
cursor: pointer;
white-space: nowrap;
}
[data-slot="tier-switch"] button {
color: var(--go-console-secondary);
border-radius: 4px;
transition:
background-color 180ms ease,
opacity 180ms ease;
}
[data-slot="tier-switch"] button[aria-pressed="false"] {
opacity: 0.4;
font-weight: 440;
}
[data-slot="tier-switch"] button[aria-pressed="false"]:is(:hover, :focus-visible) {
opacity: 0.75;
}
[data-slot="tier-switch"] button[aria-pressed="true"] {
background: var(--go-console-surface);
color: var(--go-console-text);
}
[data-slot="tier-switch"] button:hover {
color: var(--go-console-text);
}
[data-slot="expand"][aria-expanded="true"] svg {
transform: rotate(180deg);
}
@ -148,28 +138,48 @@
border-start-end-radius: 6px;
border-end-end-radius: 6px;
}
tbody tr > * {
transition: background-color 120ms ease;
}
tbody tr:hover > * {
background: var(--go-console-surface);
}
tbody tr > :first-child {
border-start-start-radius: 6px;
border-end-start-radius: 6px;
}
tbody tr > :last-child {
border-start-end-radius: 6px;
border-end-end-radius: 6px;
}
tbody th {
padding: 0.5rem 1rem;
text-align: start;
font-weight: 400;
color: var(--go-console-text);
}
[data-slot="model"] {
display: inline-flex;
[data-slot="labels"] {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.375rem;
gap: 0.25rem;
margin-top: 0.25rem;
}
[data-slot="model"] small {
[data-slot="labels"] :is(small, a) {
border: 1px solid var(--go-console-border);
padding: 0 0.25rem;
font-size: 0.625rem;
line-height: 1rem;
}
[data-slot="model"] a {
display: inline-flex;
align-items: center;
color: var(--go-console-muted);
[data-slot="labels"] a {
color: inherit;
text-decoration: none;
transition:
background-color 180ms ease,
border-color 180ms ease;
}
[data-slot="labels"] a:hover {
background: var(--go-console-track);
border-color: var(--go-console-track);
}
[data-slot="bars"] {
position: relative;
@ -178,32 +188,20 @@
position: absolute;
inset-block: 0;
border-inline-start: 1px dashed var(--go-console-border);
transition: inset-inline-start 600ms cubic-bezier(0.22, 1, 0.36, 1);
}
[data-slot="track"] {
position: relative;
display: flex;
gap: 0.25rem;
height: 0.5rem;
display: grid;
grid-template-rows: 0.5rem 0.5rem;
row-gap: 0.5rem;
margin: 0.5rem 0;
}
[data-slot="track"] > [data-tier] {
position: relative;
[data-slot="lane"] {
display: flex;
gap: 0.25rem;
}
[data-slot="lane"] > [data-tier] {
border-radius: 2px;
background: linear-gradient(var(--v2-yellow-500), var(--v2-yellow-600));
transition: width 600ms cubic-bezier(0.22, 1, 0.36, 1);
}
[data-slot="track"] > [data-tier]::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
background: linear-gradient(var(--v2-orange-500), var(--v2-orange-600));
opacity: 0;
transition: opacity 300ms ease;
}
[data-slot="track"] > [data-tier="plus"]::after {
opacity: 1;
}
[data-slot="remainder"] {
flex: 1;
@ -215,12 +213,13 @@
text-align: end;
white-space: nowrap;
}
:is(thead th, [data-slot="number"]):last-child {
padding-inline-start: 0.5rem;
}
[data-slot="number"] span {
display: block;
line-height: 1.25rem;
}
[data-slot="plan-value"] {
animation: go-plan-value-in 300ms ease-out both;
/* 13px digits are ~9.5px tall, so this leaves the same ~8px visual gap as the bars. */
line-height: 1.1rem;
}
[data-slot="axis"] {
position: relative;
@ -232,7 +231,6 @@
position: absolute;
top: 0.25rem;
transform: translateX(-50%);
transition: inset-inline-start 600ms cubic-bezier(0.22, 1, 0.36, 1);
}
&:dir(rtl) [data-slot="axis"] span {
transform: translateX(50%);
@ -312,28 +310,18 @@
[data-slot="number"] {
padding-inline: 0.5rem;
}
}
@media (prefers-reduced-motion: reduce) {
[data-slot="tier-switch"] button,
[data-slot="gridline"],
[data-slot="track"] > [data-tier],
[data-slot="track"] > [data-tier]::after,
[data-slot="axis"] span {
transition: none;
/* Rows are grids here, so highlight the row itself instead of each cell. */
tbody tr {
border-radius: 6px;
transition: background-color 120ms ease;
}
[data-slot="plan-value"] {
animation: none;
tbody tr:hover {
background: var(--go-console-surface);
}
tbody tr > *,
tbody tr:hover > * {
border-radius: 0;
background: none;
}
}
}
@keyframes go-plan-value-in {
from {
opacity: 0.3;
transform: translateY(3px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

View file

@ -5,22 +5,17 @@ import { useLanguage } from "~/context/language"
import { goPlanModels } from "./go-models"
const ticks = [100, 1000, 10000]
const max = Math.max(...goPlanModels.map((model) => model.plusRequests).filter(Number.isFinite))
export function GoPlanChart(props: { href: string }) {
const i18n = useI18n()
const language = useLanguage()
const id = createUniqueId()
const [expanded, setExpanded] = createSignal(false)
const [tier, setTier] = createSignal<"go" | "go-plus">("go")
const models = createMemo(() => goPlanModels.filter((model) => expanded() || model.featured))
const max = createMemo(() =>
Math.max(
...goPlanModels.map((model) => (tier() === "go" ? model.requests : model.plusRequests)).filter(Number.isFinite),
),
)
const position = (requests: number) =>
Number.isFinite(requests)
? 4 + Math.pow(Math.log10(Math.max(requests / 100, 1)) / Math.log10(max() / 100), 2.2) * 78
? 4 + Math.pow(Math.log10(Math.max(requests / 100, 1)) / Math.log10(max / 100), 2.2) * 78
: 100
const format = createMemo(() => new Intl.NumberFormat(language.tag(language.locale())))
const currency = createMemo(
@ -40,24 +35,24 @@ export function GoPlanChart(props: { href: string }) {
<h2 id={`${id}-title`}>{i18n.t("go.plans.limits")}</h2>
<p>{i18n.t("go.plans.description")}</p>
</div>
<div data-slot="tier-switch" role="group" aria-label={i18n.t("go.plans.legend")}>
<button type="button" aria-pressed={tier() === "go"} onClick={() => setTier("go")}>
<ul data-slot="legend" aria-label={i18n.t("go.plans.legend")}>
<li>
<i data-tier="go" />
Go
</button>
<button type="button" aria-pressed={tier() === "go-plus"} onClick={() => setTier("go-plus")}>
</li>
<li>
<i data-tier="plus" />
Go Plus
</button>
</div>
</li>
</ul>
</div>
<div data-slot="scroll">
<table id={id}>
<colgroup>
<col style={{ width: "26%" }} />
<col style={{ width: "40%" }} />
<col style={{ width: "17%" }} />
<col style={{ width: "17%" }} />
<col style={{ width: "44%" }} />
<col style={{ width: "16%" }} />
<col style={{ width: "14%" }} />
</colgroup>
<thead>
<tr>
@ -73,60 +68,55 @@ export function GoPlanChart(props: { href: string }) {
{(model) => (
<tr>
<th scope="row">
<span data-slot="model">
<bdi>{model.name}</bdi>
<Show when={model.fresh}>
<small>{i18n.t("go.graph.new")}</small>
</Show>
<Show when={model.limitedTime}>
<small>{i18n.t("go.graph.limitedTime")}</small>
</Show>
<Show when={model.regions}>
<a
href="https://ai.developer.meta.com/legal/geographic-use-policy"
title={i18n.t("go.graph.limitedRegions")}
aria-label={`${model.name}: ${i18n.t("go.graph.limitedRegions")}`}
>
<svg viewBox="0 0 16 16" width="13" height="13" fill="none" aria-hidden="true">
<circle cx="8" cy="8" r="6" stroke="currentColor" />
<ellipse cx="8" cy="8" rx="2.5" ry="6" stroke="currentColor" />
<path d="M2 8h12" stroke="currentColor" />
</svg>
</a>
</Show>
</span>
<bdi>{model.name}</bdi>
<Show when={model.fresh || model.limitedTime || model.regions}>
<span data-slot="labels">
<Show when={model.fresh}>
<small>{i18n.t("go.graph.new")}</small>
</Show>
<Show when={model.limitedTime}>
<small>{i18n.t("go.graph.limitedTime")}</small>
</Show>
<Show when={model.regions}>
<a href="https://ai.developer.meta.com/legal/geographic-use-policy">
{i18n.t("go.graph.limitedRegions")}
</a>
</Show>
</span>
</Show>
</th>
<td data-slot="bars" aria-hidden="true">
<For each={ticks}>
{(tick) => <span data-slot="gridline" style={{ "inset-inline-start": `${position(tick)}%` }} />}
</For>
<div data-slot="track">
<span
data-tier={tier() === "go" ? "go" : "plus"}
style={{ width: `${position(tier() === "go" ? model.requests : model.plusRequests)}%` }}
/>
<span data-slot="remainder" />
<div data-slot="lane">
{/* The Go bar is a linear fraction of the Plus bar so the multiplier reads true on the log scale. */}
<span
data-tier="go"
style={{
width: `${
Number.isFinite(model.plusRequests)
? (position(model.plusRequests) * model.requests) / model.plusRequests
: position(model.requests)
}%`,
}}
/>
<span data-slot="remainder" />
</div>
<div data-slot="lane">
<span data-tier="plus" style={{ width: `${position(model.plusRequests)}%` }} />
<span data-slot="remainder" />
</div>
</div>
</td>
<td data-slot="number">
<Show when={tier()} keyed>
{(selected) => (
<span data-slot="plan-value">
{format().format(selected === "go" ? model.requests : model.plusRequests)}
</span>
)}
</Show>
<span>{format().format(model.requests)}</span>
<span>{format().format(model.plusRequests)}</span>
</td>
<td data-slot="number">
<Show when={tier()} keyed>
{(selected) => (
<span data-slot="plan-value">
{Number.isFinite(selected === "go" ? model.allowance : model.plusAllowance)
? currency().format(selected === "go" ? model.allowance : model.plusAllowance)
: "∞"}
</span>
)}
</Show>
<span>{Number.isFinite(model.allowance) ? currency().format(model.allowance) : "∞"}</span>
<span>{Number.isFinite(model.plusAllowance) ? currency().format(model.plusAllowance) : "∞"}</span>
</td>
</tr>
)}

View file

@ -292,8 +292,8 @@ export const dict = {
"go.graph.scale": "Nonlinear request scale",
"go.graph.showAll": "View all {{count}} models",
"go.graph.showLess": "Show fewer models",
"go.graph.limitedRegions": "limited regions",
"go.graph.limitedTime": "limited time",
"go.graph.limitedRegions": "Limited Regions",
"go.graph.limitedTime": "Limited Time",
"go.graph.tick": "{{n}}x",
"go.graph.usageLimits": "Usage limits",
"go.graph.aria": "Requests per 5h: {{free}} vs {{go}}",