diff --git a/bun.lock b/bun.lock index c8cb37f4e50..71049cb1ac2 100644 --- a/bun.lock +++ b/bun.lock @@ -178,6 +178,7 @@ "@upstash/redis": "1.38.0", "chart.js": "4.5.1", "nitro": "3.0.1-alpha.1", + "number-flow": "0.6.2", "solid-js": "catalog:", "solid-list": "0.3.0", "solid-stripe": "0.8.1", @@ -3651,6 +3652,8 @@ "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], "estree-util-attach-comments": ["estree-util-attach-comments@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw=="], @@ -4565,6 +4568,8 @@ "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + "number-flow": ["number-flow@0.6.2", "", { "dependencies": { "esm-env": "^1.1.4" } }, "sha512-MCnImG4Q5vPwhSXnov56nOuyyKn6LC+Qd7II1UiKc+ACRtug5iAtn0+CwXNxM38AC5lSowEY+oYEtZX2qMnUyw=="], + "nypm": ["nypm@0.6.6", "", { "dependencies": { "citty": "^0.2.2", "pathe": "^2.0.3", "tinyexec": "^1.1.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-vRyr0r4cbBapw07Xw8xrj9Teq3o7MUD35rSaTcanDbW+aK2XHDgJFiU6ZTj2GBw7Q12ysdsyFss+Vdz4hQ0Y6Q=="], "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], diff --git a/packages/console/app/package.json b/packages/console/app/package.json index e83f568c3be..8e03034d621 100644 --- a/packages/console/app/package.json +++ b/packages/console/app/package.json @@ -29,6 +29,7 @@ "@upstash/redis": "1.38.0", "chart.js": "4.5.1", "nitro": "3.0.1-alpha.1", + "number-flow": "0.6.2", "solid-js": "catalog:", "solid-list": "0.3.0", "solid-stripe": "0.8.1", diff --git a/packages/console/app/src/component/limits-graph.tsx b/packages/console/app/src/component/limits-graph.tsx new file mode 100644 index 00000000000..03e27893976 --- /dev/null +++ b/packages/console/app/src/component/limits-graph.tsx @@ -0,0 +1,267 @@ +import { For, createSignal, onCleanup, onMount } from "solid-js" +import { useI18n } from "~/context/i18n" +import { RollingNumber } from "./rolling-number" + +export function LimitsGraph(props: { href: string }) { + let root!: HTMLElement + const [visible, setVisible] = createSignal(false) + const [boosted, setBoosted] = createSignal(false) + const [promoted, setPromoted] = createSignal([]) + let timer: ReturnType | undefined + + const i18n = useI18n() + + onMount(() => { + const motion = window.matchMedia("(prefers-reduced-motion: reduce)") + const finish = () => { + if (!motion.matches) return + clearTimeout(timer) + setVisible(true) + setBoosted(true) + setPromoted(bonuses.map((model) => model.id)) + } + motion.addEventListener("change", finish) + onCleanup(() => { + clearTimeout(timer) + motion.removeEventListener("change", finish) + }) + if (motion.matches) return finish() + if (typeof IntersectionObserver === "undefined") return setVisible(true) + const observer = new IntersectionObserver( + (entries) => { + const entry = entries[0] + if (!entry?.isIntersecting || entry.intersectionRatio < 0.35) return + setVisible(true) + observer.disconnect() + }, + { threshold: 0.35 }, + ) + observer.observe(root) + onCleanup(() => observer.disconnect()) + }) + + const baseline = 100 + const graph = [ + { id: "kimi-k3", name: "Kimi K3", req: 110 }, + { id: "grok-4.6", name: "Grok 4.6", req: 169 }, + { id: "hy4-preview", name: "Hy4 preview", req: 1350 }, + { id: "gpt-5.6-luna", name: "GPT 5.6 Luna", req: 2050 }, + { id: "glm-5.3-flash", name: "GLM-5.3-Flash", req: 3160, baseReq: 1580, bonus: "2x usage" }, + { id: "minimax-m3", name: "MiniMax M3", req: 3200 }, + { id: "qwen3.7-plus", name: "Qwen3.7 Plus", req: 4300 }, + { id: "qwen3.8-flash", name: "Qwen3.8 Flash", req: 5400 }, + { id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", req: 7600 }, + { id: "longcat-2.0", name: "LongCat-2.0", req: 11400 }, + { id: "mimo-v2.5", name: "MiMo-V2.5", req: 30100 }, + { id: "hy3", name: "Hy3", req: 34400, baseReq: 4300, bonus: "8x usage" }, + { id: "muse-spark-1.2-contributor", name: "Muse Spark 1.2 Contributor", req: 45300, edge: true }, + ].map((model, index) => ({ ...model, d: `${50 + index * 25}ms` })) + const bonuses = graph.filter((model) => model.baseReq) + + const w = 1040 + const chartW = 720 + const left = 40 + const right = 60 + const top = 18 + const bottom = 44 + const plot = chartW - left - right + const infiniteX = w - 180 + + const ratio = (n: number) => n / baseline + const rmax = Math.max(1, ...graph.filter((m) => !("infinite" in m)).map((m) => ratio(m.req))) + const log = (n: number) => Math.log10(Math.max(n, 1)) + const base = 24 + const p = 2.2 + const x = (r: number) => left + base + Math.pow(log(r) / log(rmax), p) * (plot - base) + const ticks = [1, 5, 10, 25, 50, 100, 250].filter((t) => t <= rmax) + const labels = (() => { + const set = new Set() + let last = -Infinity + for (const t of ticks) { + if (t === 1) { + set.add(t) + last = x(t) + continue + } + const pos = x(t) + if (pos - last < 44) continue + set.add(t) + last = pos + } + return set + })() + const shown = ticks.filter((t) => labels.has(t)) + const bh = 8 + const gap = 20 + const step = bh + gap + const gy = (i: number) => top + 22 + step * i + const h = gy(graph.length - 1) + bottom + const my = graph.length < 2 ? gy(0) : (gy(0) + gy(graph.length - 1)) / 2 + const px = (n: number) => `${(n / w) * 100}%` + const py = (n: number) => `${(n / h) * 100}%` + const lx = px(left - 16) + const ty = py(h - 18) + const timing = () => { + const style = getComputedStyle(root) + return { + duration: Number.parseFloat(style.getPropertyValue("--bonus-duration")), + easing: style.getPropertyValue("--spring-easing").trim(), + spinEasing: style.getPropertyValue("--digit-easing").trim(), + } + } + + return ( +
{ + if (!(event.target instanceof SVGElement) || event.animationName !== "go-graph-reveal") return + if (event.target.hasAttribute("data-stage-end") && !boosted()) { + const duration = Number.parseFloat(getComputedStyle(root).getPropertyValue("--reveal-duration")) + timer = setTimeout(() => setBoosted(true), duration * 0.6) + return + } + if (event.target.dataset.animate !== "bonus") return + const model = event.target.dataset.model + if (!model) return + setPromoted((current) => [...current, model]) + }} + > +
+ + + + + + +
+ + {(m, i) => ( + + + {!("infinite" in m) && m.baseReq ? ( + + ) : ( + {"infinite" in m ? "\u221e" : m.req.toLocaleString()} + )} + {m.name} + {m.id === "muse-spark-1.2-contributor" && ( + + ( + + {i18n.t("go.graph.limitedRegions")} + + ) + + )} + {"infinite" in m && ({i18n.t("go.graph.limitedTime")})} + + {"bonus" in m && {m.bonus}} + + )} + +
+
+ +
+
+
+
+ {i18n.t("go.graph.label")} + + {i18n.t("go.graph.usageLimits")} + +
+
+
+
+
+ ) +} diff --git a/packages/console/app/src/component/rolling-number.tsx b/packages/console/app/src/component/rolling-number.tsx new file mode 100644 index 00000000000..df1af6ca20c --- /dev/null +++ b/packages/console/app/src/component/rolling-number.tsx @@ -0,0 +1,58 @@ +import NumberFlow from "number-flow" +import { continuous } from "number-flow/plugins" +import { createEffect, onCleanup, onMount } from "solid-js" + +export function RollingNumber(props: { + value: number + target: number + timing: () => { duration: number; easing: string; spinEasing: string } +}) { + let root!: HTMLSpanElement + // Keep the server-rendered text stable while the custom element owns its contents. + const initial = props.value.toLocaleString() + const growing = props.target.toLocaleString().length > initial.length + + onMount(() => { + if (new Intl.NumberFormat().resolvedOptions().numberingSystem !== "latn") { + createEffect(() => { + root.textContent = props.value.toLocaleString() + }) + return + } + + const flow = new NumberFlow() + flow.format = { useGrouping: true, maximumFractionDigits: 0 } + flow.trend = 1 + if (growing) flow.plugins = [continuous] + flow.opacityTiming = { duration: 180, easing: "ease-out" } + const motion = window.matchMedia("(prefers-reduced-motion: reduce)") + const updateMotion = () => { + flow.animated = !motion.matches + } + updateMotion() + motion.addEventListener("change", updateMotion) + onCleanup(() => motion.removeEventListener("change", updateMotion)) + root.replaceChildren(flow) + createEffect(() => { + const value = props.value + if (flow.value === value) return + const timing = props.timing() + flow.transformTiming = { duration: timing.duration, easing: timing.easing } + flow.spinTiming = { + duration: timing.duration, + easing: growing ? "cubic-bezier(0.45, 0, 0.55, 1)" : timing.spinEasing, + } + flow.update(value) + }) + }) + + return ( + + {initial} + + ) +} diff --git a/packages/console/app/src/i18n/en.ts b/packages/console/app/src/i18n/en.ts index a557d4fb0e8..a479f2642b9 100644 --- a/packages/console/app/src/i18n/en.ts +++ b/packages/console/app/src/i18n/en.ts @@ -267,7 +267,7 @@ export const dict = { "go.graph.free": "Free", "go.graph.freePill": "Big Pickle and free models", "go.graph.go": "Go", - "go.graph.label": "Requests per 5 hour", + "go.graph.label": "Requests / 5 hours", "go.graph.limitedRegions": "limited regions", "go.graph.limitedTime": "limited time", "go.graph.tick": "{{n}}x", diff --git a/packages/console/app/src/routes/go/index.css b/packages/console/app/src/routes/go/index.css index b72b01395a1..7f70c41c1de 100644 --- a/packages/console/app/src/routes/go/index.css +++ b/packages/console/app/src/routes/go/index.css @@ -21,10 +21,40 @@ } } -@keyframes go-graph-bar { +@keyframes go-graph-reveal { to { + clip-path: inset(0 0 0 0) fill-box; + } +} + +@keyframes go-graph-label { + to { + mask-position: 0 0; opacity: 1; - transform: scaleX(1); + } +} + +@keyframes go-graph-grid { + to { + mask-position: 0 100%; + } +} + +@keyframes go-graph-heat { + from { + filter: brightness(var(--arrival-brightness)); + } + to { + filter: brightness(1); + } +} + +@keyframes go-graph-bonus-heat { + from { + color: var(--color-go-3); + } + to { + color: var(--color-text-weak); } } @@ -477,6 +507,48 @@ body { } [data-component="limit-graph"] { + --reveal-duration: 950ms; + --bonus-duration: 1500ms; + --digit-easing: cubic-bezier(0.22, 1, 0.36, 1); + --grid-duration: 2000ms; + --grid-easing: cubic-bezier(0.22, 1, 0.36, 1); + --arrival-brightness: 1.15; + --heat-duration: 1600ms; + --bar-delay: 240ms; + /* Critically damped response, shared by the bars, labels, and rolling digits. */ + --spring-easing: linear( + 0, + 0.02991, + 0.10078, + 0.19179, + 0.28962, + 0.38611, + 0.47651, + 0.55839, + 0.63079, + 0.69365, + 0.74748, + 0.79306, + 0.83131, + 0.86315, + 0.8895, + 0.91117, + 0.92892, + 0.94339, + 0.95515, + 0.96467, + 0.97236, + 0.97855, + 0.98352, + 0.98751, + 0.9907, + 0.99324, + 0.99527, + 0.99689, + 0.99817, + 0.99919, + 1 + ); margin: 0 auto; width: calc(100% - 120px); max-width: calc(100% - 120px); @@ -491,6 +563,7 @@ body { } [data-slot="plot"] { + container-type: inline-size; position: relative; overflow: visible; width: 100%; @@ -515,6 +588,7 @@ body { } [data-slot="xlabels"] [data-xlabel] { + opacity: 0; position: absolute; left: var(--x); top: var(--y); @@ -551,11 +625,26 @@ body { } } + [data-slot="xlabels"] [data-xlabel], + [data-slot="pills"] [data-label], + [data-bonus] { + mask-image: linear-gradient(to right, #000 40%, transparent 60%); + mask-size: 250% 100%; + mask-position: 100% 0; + mask-repeat: no-repeat; + } + [data-slot="pills"] { position: absolute; inset: 0; pointer-events: none; + [data-label] { + display: inline-flex; + align-items: center; + gap: 8px; + } + [data-item] { position: absolute; left: var(--x); @@ -573,7 +662,6 @@ body { font-size: 13px; line-height: 20px; box-sizing: border-box; - opacity: 0; } @media (max-width: 60rem) { @@ -605,10 +693,16 @@ body { [data-value] { color: var(--color-text-strong); font-weight: 600; + font-variant-numeric: tabular-nums; white-space: nowrap; + + number-flow { + line-height: 1; + } } [data-bonus] { + opacity: 0; color: var(--color-text-weak); font-size: 12px; font-weight: 400; @@ -685,11 +779,22 @@ body { opacity: 0.55; } + [data-grid], + [data-stub] { + mask-image: linear-gradient(to top, #000 48%, transparent 52%); + mask-size: 100% 250%; + mask-position: 0 0; + mask-repeat: no-repeat; + mask-origin: stroke-box; + mask-clip: stroke-box; + } + + [data-animate="bar"], + [data-animate="bonus"] { + clip-path: inset(0 100% 0 0) fill-box; + } + [data-bar] { - transform-box: fill-box; - transform-origin: left center; - opacity: 0; - transform: scaleX(0.02); fill: var(--bar-go); stroke: none; } @@ -760,18 +865,74 @@ body { animation-delay: var(--d, 0ms); } - &[data-visible] [data-bar] { - animation: go-graph-bar 560ms cubic-bezier(0.2, 0.7, 0.2, 1) forwards; + &[data-visible] [data-grid], + &[data-visible] [data-stub] { + animation: + go-graph-grid var(--grid-duration) var(--grid-easing) forwards, + go-graph-heat var(--heat-duration) linear backwards; + animation-delay: var(--d, 0ms), calc(var(--d, 0ms) + var(--grid-duration)); + } + + &[data-visible] [data-slot="xlabels"] [data-xlabel] { + animation: go-graph-label 300ms ease-out forwards; animation-delay: var(--d, 0ms); } - &[data-visible] [data-slot="pills"] [data-item] { - opacity: 1; - transition: opacity 240ms ease; - transition-delay: var(--d, 0ms); + &[data-visible] [data-animate="bar"] { + animation: + go-graph-reveal var(--reveal-duration) var(--spring-easing) forwards, + go-graph-heat var(--heat-duration) linear backwards; + animation-delay: + calc(var(--bar-delay) + var(--d, 0ms)), calc(var(--bar-delay) + var(--d, 0ms) + var(--reveal-duration)); + } + + &[data-visible] [data-slot="pills"] [data-label] { + animation: go-graph-label 400ms ease-out forwards; + animation-delay: calc(var(--bar-delay) + var(--d, 0ms) + var(--reveal-duration) * 0.4); + } + + &[data-boosted] [data-animate="bonus"] { + animation: + go-graph-reveal var(--bonus-duration) var(--spring-easing) forwards, + go-graph-heat var(--heat-duration) linear backwards; + animation-delay: var(--bonus-delay), calc(var(--bonus-delay) + var(--bonus-duration)); + } + + &[data-boosted] [data-slot="pills"] [data-item][data-promo] { + translate: var(--travel) 0; + transition: translate var(--bonus-duration) var(--spring-easing) var(--bonus-delay); + + @media (max-width: 60rem) { + &[data-edge] { + translate: none; + transition: none; + } + } + } + + &[data-boosted] [data-bonus] { + animation: + go-graph-label 400ms ease-out forwards, + go-graph-bonus-heat 3000ms linear backwards; + animation-delay: var(--bonus-delay); } @media (prefers-reduced-motion: reduce) { + [data-grid], + [data-stub], + &[data-visible] [data-grid], + &[data-visible] [data-stub] { + mask-image: none; + animation: none; + } + + [data-slot="xlabels"] [data-xlabel], + &[data-visible] [data-slot="xlabels"] [data-xlabel] { + opacity: 1; + mask-image: none; + animation: none; + } + [data-animate="line"] { stroke-dashoffset: 0; animation: none; @@ -781,9 +942,12 @@ body { transform: none; animation: none; } - [data-bar] { + [data-animate="bar"], + &[data-visible] [data-animate="bar"], + [data-animate="bonus"], + &[data-boosted] [data-animate="bonus"] { opacity: 1; - transform: none; + clip-path: none; animation: none; } [data-row], @@ -792,8 +956,16 @@ body { transition: none; } - [data-slot="pills"] [data-item] { + [data-slot="pills"] [data-label], + &[data-visible] [data-slot="pills"] [data-label], + [data-bonus], + &[data-boosted] [data-bonus] { opacity: 1; + mask-image: none; + animation: none; + } + + &[data-boosted] [data-slot="pills"] [data-item][data-promo] { transition: none; } } @@ -886,6 +1058,12 @@ body { flex-wrap: wrap; justify-content: flex-start; + [data-label] { + max-width: 100%; + flex-wrap: wrap; + gap: 3px 8px; + } + &[data-model="muse-spark-1.2-contributor"] { transform: translateY(11px); diff --git a/packages/console/app/src/routes/go/index.tsx b/packages/console/app/src/routes/go/index.tsx index b2e67ecc6e4..6b356ccafa1 100644 --- a/packages/console/app/src/routes/go/index.tsx +++ b/packages/console/app/src/routes/go/index.tsx @@ -1,7 +1,7 @@ import "./index.css" import { createAsync, query } from "@solidjs/router" import { Title, Meta } from "@solidjs/meta" -import { For, createMemo, createSignal, onCleanup, onMount } from "solid-js" +import { For, createMemo } from "solid-js" //import { HttpHeader } from "@solidjs/start" import goLogoLight from "../../asset/go-ornate-light.svg" import goLogoDark from "../../asset/go-ornate-dark.svg" @@ -10,6 +10,7 @@ import { Faq } from "~/component/faq" import { Legal } from "~/component/legal" import { Footer } from "~/component/footer" import { Header } from "~/component/header" +import { LimitsGraph } from "~/component/limits-graph" import { config } from "~/config" import { getLastSeenWorkspaceID } from "../workspace/common" import { IconMiniMax, IconMiMo, IconZai, IconAlibaba, IconDeepSeek } from "~/component/icon" @@ -49,203 +50,6 @@ const models = [ { name: "Hy3", training: "go.faq.a5.notUsed", retention: "go.faq.a5.retention0" }, ] as const -function LimitsGraph(props: { href: string }) { - let root!: HTMLElement - const [visible, setVisible] = createSignal(false) - - const i18n = useI18n() - - onMount(() => { - if (typeof IntersectionObserver === "undefined") return setVisible(true) - const observer = new IntersectionObserver( - (entries) => { - const entry = entries[0] - if (!entry?.isIntersecting) return - setVisible(true) - observer.disconnect() - }, - { threshold: 0.35 }, - ) - observer.observe(root) - onCleanup(() => observer.disconnect()) - }) - - const baseline = 100 - const graph = [ - { id: "kimi-k3", name: "Kimi K3", req: 110, d: "50ms" }, - { id: "grok-4.6", name: "Grok 4.6", req: 169, d: "75ms" }, - { id: "hy4-preview", name: "Hy4 preview", req: 1350, d: "90ms" }, - { id: "gpt-5.6-luna", name: "GPT 5.6 Luna", req: 2050, d: "290ms" }, - { id: "glm-5.3-flash", name: "GLM-5.3-Flash", req: 3160, baseReq: 1580, bonus: "2x usage", d: "100ms" }, - { id: "minimax-m3", name: "MiniMax M3", req: 3200, d: "210ms" }, - { id: "qwen3.7-plus", name: "Qwen3.7 Plus", req: 4300, d: "300ms" }, - { id: "qwen3.8-flash", name: "Qwen3.8 Flash", req: 5400, d: "315ms" }, - { id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", req: 7600, d: "330ms" }, - { id: "longcat-2.0", name: "LongCat-2.0", req: 11400, d: "335ms" }, - { id: "mimo-v2.5", name: "MiMo-V2.5", req: 30100, d: "340ms" }, - { id: "hy3", name: "Hy3", req: 34400, baseReq: 4300, bonus: "8x usage", d: "320ms" }, - { id: "muse-spark-1.2-contributor", name: "Muse Spark 1.2 Contributor", req: 45300, edge: true, d: "360ms" }, - ] - - const w = 1040 - const chartW = 720 - const left = 40 - const right = 60 - const top = 18 - const bottom = 44 - const plot = chartW - left - right - const infiniteX = w - 180 - - const ratio = (n: number) => n / baseline - const rmax = Math.max(1, ...graph.filter((m) => !("infinite" in m)).map((m) => ratio(m.req))) - const log = (n: number) => Math.log10(Math.max(n, 1)) - const base = 24 - const p = 2.2 - const x = (r: number) => left + base + Math.pow(log(r) / log(rmax), p) * (plot - base) - const ticks = [1, 5, 10, 25, 50, 100, 250].filter((t) => t <= rmax) - const labels = (() => { - const set = new Set() - let last = -Infinity - for (const t of ticks) { - if (t === 1) { - set.add(t) - last = x(t) - continue - } - const pos = x(t) - if (pos - last < 44) continue - set.add(t) - last = pos - } - return set - })() - const shown = ticks.filter((t) => labels.has(t)) - const bh = 8 - const gap = 20 - const step = bh + gap - const gy = (i: number) => top + 22 + step * i - const h = gy(graph.length - 1) + bottom - const my = graph.length < 2 ? gy(0) : (gy(0) + gy(graph.length - 1)) / 2 - const px = (n: number) => `${(n / w) * 100}%` - const py = (n: number) => `${(n / h) * 100}%` - const lx = px(left - 16) - const ty = py(h - 18) - - return ( -
-
- - - - - - -
- - {(m, i) => ( - - {"infinite" in m ? "∞" : m.req.toLocaleString()} - {m.name} - {m.id === "muse-spark-1.2-contributor" && ( - - ( - - {i18n.t("go.graph.limitedRegions")} - - ) - - )} - {"infinite" in m && ({i18n.t("go.graph.limitedTime")})} - {"bonus" in m && {m.bonus}} - - )} - -
-
- -
-
-
-
- {i18n.t("go.graph.label")} - - {i18n.t("go.graph.usageLimits")} - -
-
-
-
-
- ) -} - export default function Home() { const workspaceID = createAsync(() => checkLoggedIn()) const subscribeUrl = createMemo(() => (workspaceID() ? `/workspace/${workspaceID()}/go` : "/auth"))