feat(aircoding): AirCoding V2 baseline — deterministic multi-agent architecture
Forked from OpenCode v1.17.4 with multi-agent system: - 5 agents: aircoding, scheduler, worker, architect, reviewer - Deterministic DAG scheduling engine (coordinator_tick) - Tool whitelists as hard enforcement - AirCoding validation plugin - V1 requirements: C4 docs, ADR, AGENTS.md, debug-log.md - Design documents in docs/
This commit is contained in:
123
packages/stats/app/src/app.css
Normal file
123
packages/stats/app/src/app.css
Normal file
@@ -0,0 +1,123 @@
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
--stats-bg: #f8f5ee;
|
||||
--stats-ink: #16110d;
|
||||
--stats-muted: #6d6257;
|
||||
--stats-line: #ded5c9;
|
||||
--stats-panel: #fffaf1;
|
||||
--stats-accent: #2357ff;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--stats-bg: #11100e;
|
||||
--stats-ink: #f7efe4;
|
||||
--stats-muted: #b8aa99;
|
||||
--stats-line: #322d27;
|
||||
--stats-panel: #1a1714;
|
||||
--stats-accent: #86a2ff;
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
line-height: 1;
|
||||
background: var(--stats-bg);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-width: 320px;
|
||||
background:
|
||||
radial-gradient(circle at top left, color-mix(in srgb, var(--stats-accent) 16%, transparent), transparent 32rem),
|
||||
var(--stats-bg);
|
||||
color: var(--stats-ink);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.shell {
|
||||
box-sizing: border-box;
|
||||
min-height: 100vh;
|
||||
padding: 2rem clamp(1rem, 4vw, 4rem);
|
||||
}
|
||||
|
||||
.panel {
|
||||
display: grid;
|
||||
gap: clamp(2rem, 8vw, 5rem);
|
||||
box-sizing: border-box;
|
||||
width: min(100%, 72rem);
|
||||
margin: 0 auto;
|
||||
padding: clamp(1.25rem, 4vw, 3rem);
|
||||
border: 1px solid var(--stats-line);
|
||||
border-radius: 1.5rem;
|
||||
background: color-mix(in srgb, var(--stats-panel) 88%, transparent);
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 1rem;
|
||||
color: var(--stats-muted);
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1 {
|
||||
max-width: 11ch;
|
||||
margin: 0;
|
||||
font-size: clamp(3rem, 14vw, 9rem);
|
||||
line-height: 0.85;
|
||||
letter-spacing: -0.08em;
|
||||
}
|
||||
|
||||
.summary {
|
||||
max-width: 42rem;
|
||||
margin: 1.5rem 0 0;
|
||||
color: var(--stats-muted);
|
||||
font-size: clamp(1rem, 2vw, 1.25rem);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 1px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--stats-line);
|
||||
border-radius: 1rem;
|
||||
background: var(--stats-line);
|
||||
}
|
||||
|
||||
.metric {
|
||||
padding: 1rem;
|
||||
background: var(--stats-panel);
|
||||
}
|
||||
|
||||
.metric b {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: clamp(1.5rem, 4vw, 3rem);
|
||||
letter-spacing: -0.05em;
|
||||
}
|
||||
|
||||
.metric span {
|
||||
color: var(--stats-muted);
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.link {
|
||||
display: inline-flex;
|
||||
width: fit-content;
|
||||
margin-top: 1.5rem;
|
||||
color: var(--stats-accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
31
packages/stats/app/src/app.tsx
Normal file
31
packages/stats/app/src/app.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { MetaProvider, Meta, Title } from "@solidjs/meta"
|
||||
import { Router } from "@solidjs/router"
|
||||
import { FileRoutes } from "@solidjs/start/router"
|
||||
import { Suspense } from "solid-js"
|
||||
import "./app.css"
|
||||
|
||||
function AppMeta() {
|
||||
return (
|
||||
<>
|
||||
<Title>OpenCode Data</Title>
|
||||
<Meta name="description" content="OpenCode usage data, market share, token cost, and session cost." />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Router
|
||||
base={import.meta.env.BASE_URL.replace(/\/$/, "")}
|
||||
explicitLinks={true}
|
||||
root={(props) => (
|
||||
<MetaProvider>
|
||||
<AppMeta />
|
||||
<Suspense>{props.children}</Suspense>
|
||||
</MetaProvider>
|
||||
)}
|
||||
>
|
||||
<FileRoutes />
|
||||
</Router>
|
||||
)
|
||||
}
|
||||
18
packages/stats/app/src/asset/logo-ornate-dark.svg
Normal file
18
packages/stats/app/src/asset/logo-ornate-dark.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<svg width="234" height="42" viewBox="0 0 234 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 30H6V18H18V30Z" fill="#4B4646"/>
|
||||
<path d="M18 12H6V30H18V12ZM24 36H0V6H24V36Z" fill="#B7B1B1"/>
|
||||
<path d="M48 30H36V18H48V30Z" fill="#4B4646"/>
|
||||
<path d="M36 30H48V12H36V30ZM54 36H36V42H30V6H54V36Z" fill="#B7B1B1"/>
|
||||
<path d="M84 24V30H66V24H84Z" fill="#4B4646"/>
|
||||
<path d="M84 24H66V30H84V36H60V6H84V24ZM66 18H78V12H66V18Z" fill="#B7B1B1"/>
|
||||
<path d="M108 36H96V18H108V36Z" fill="#4B4646"/>
|
||||
<path d="M108 12H96V36H90V6H108V12ZM114 36H108V12H114V36Z" fill="#B7B1B1"/>
|
||||
<path d="M144 30H126V18H144V30Z" fill="#4B4646"/>
|
||||
<path d="M144 12H126V30H144V36H120V6H144V12Z" fill="#F1ECEC"/>
|
||||
<path d="M168 30H156V18H168V30Z" fill="#4B4646"/>
|
||||
<path d="M168 12H156V30H168V12ZM174 36H150V6H174V36Z" fill="#F1ECEC"/>
|
||||
<path d="M198 30H186V18H198V30Z" fill="#4B4646"/>
|
||||
<path d="M198 12H186V30H198V12ZM204 36H180V6H198V0H204V36Z" fill="#F1ECEC"/>
|
||||
<path d="M234 24V30H216V24H234Z" fill="#4B4646"/>
|
||||
<path d="M216 12V18H228V12H216ZM234 24H216V30H234V36H210V6H234V24Z" fill="#F1ECEC"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
18
packages/stats/app/src/asset/logo-ornate-light.svg
Normal file
18
packages/stats/app/src/asset/logo-ornate-light.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<svg width="234" height="42" viewBox="0 0 234 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 30H6V18H18V30Z" fill="#CFCECD"/>
|
||||
<path d="M18 12H6V30H18V12ZM24 36H0V6H24V36Z" fill="#656363"/>
|
||||
<path d="M48 30H36V18H48V30Z" fill="#CFCECD"/>
|
||||
<path d="M36 30H48V12H36V30ZM54 36H36V42H30V6H54V36Z" fill="#656363"/>
|
||||
<path d="M84 24V30H66V24H84Z" fill="#CFCECD"/>
|
||||
<path d="M84 24H66V30H84V36H60V6H84V24ZM66 18H78V12H66V18Z" fill="#656363"/>
|
||||
<path d="M108 36H96V18H108V36Z" fill="#CFCECD"/>
|
||||
<path d="M108 12H96V36H90V6H108V12ZM114 36H108V12H114V36Z" fill="#656363"/>
|
||||
<path d="M144 30H126V18H144V30Z" fill="#CFCECD"/>
|
||||
<path d="M144 12H126V30H144V36H120V6H144V12Z" fill="#211E1E"/>
|
||||
<path d="M168 30H156V18H168V30Z" fill="#CFCECD"/>
|
||||
<path d="M168 12H156V30H168V12ZM174 36H150V6H174V36Z" fill="#211E1E"/>
|
||||
<path d="M198 30H186V18H198V30Z" fill="#CFCECD"/>
|
||||
<path d="M198 12H186V30H198V12ZM204 36H180V6H198V0H204V36Z" fill="#211E1E"/>
|
||||
<path d="M234 24V30H216V24H234Z" fill="#CFCECD"/>
|
||||
<path d="M216 12V18H228V12H216ZM234 24H216V30H234V36H210V6H234V24Z" fill="#211E1E"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
7
packages/stats/app/src/entry-client.tsx
Normal file
7
packages/stats/app/src/entry-client.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
// @refresh reload
|
||||
import { mount, StartClient } from "@solidjs/start/client"
|
||||
|
||||
const root = document.getElementById("app")
|
||||
if (!root) throw new Error("Root element #app not found")
|
||||
|
||||
mount(() => <StartClient />, root)
|
||||
37
packages/stats/app/src/entry-server.tsx
Normal file
37
packages/stats/app/src/entry-server.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
// @refresh reload
|
||||
import { createHandler, StartServer } from "@solidjs/start/server"
|
||||
|
||||
const statsThemePreloadScript = `;(function () {
|
||||
var preference = "system"
|
||||
try {
|
||||
var stored = localStorage.getItem("opencode:stats-theme")
|
||||
if (stored === "dark" || stored === "light" || stored === "system") preference = stored
|
||||
} catch (_) {}
|
||||
document.documentElement.dataset.statsTheme = preference
|
||||
if (preference === "system") document.documentElement.style.removeProperty("color-scheme")
|
||||
else document.documentElement.style.setProperty("color-scheme", preference)
|
||||
})()`
|
||||
|
||||
export default createHandler(
|
||||
() => (
|
||||
<StartServer
|
||||
document={({ assets, children, scripts }) => (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script id="stats-theme-preload-script">{statsThemePreloadScript}</script>
|
||||
{assets}
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">{children}</div>
|
||||
{scripts}
|
||||
</body>
|
||||
</html>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
{
|
||||
mode: "async",
|
||||
},
|
||||
)
|
||||
1
packages/stats/app/src/global.d.ts
vendored
Normal file
1
packages/stats/app/src/global.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="@solidjs/start/env" />
|
||||
10
packages/stats/app/src/resource.d.ts
vendored
Normal file
10
packages/stats/app/src/resource.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import "sst/resource"
|
||||
|
||||
declare module "sst/resource" {
|
||||
export interface Resource {
|
||||
EMAILOCTOPUS_API_KEY: {
|
||||
type: "sst.sst.Secret"
|
||||
value: string
|
||||
}
|
||||
}
|
||||
}
|
||||
836
packages/stats/app/src/routes/[lab]/[model].tsx
Normal file
836
packages/stats/app/src/routes/[lab]/[model].tsx
Normal file
@@ -0,0 +1,836 @@
|
||||
import "../index.css"
|
||||
import { Link, Meta, Title } from "@solidjs/meta"
|
||||
import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
|
||||
import { geoEquirectangular, geoPath } from "d3-geo"
|
||||
import { scaleSqrt } from "d3-scale"
|
||||
import countryCodesSource from "i18n-iso-countries/codes.json?raw"
|
||||
import { feature, mesh } from "topojson-client"
|
||||
import countriesTopologySource from "world-atlas/countries-110m.json?raw"
|
||||
import {
|
||||
getStatsModelData,
|
||||
type CountryEntry,
|
||||
type ModelPeerEntry,
|
||||
type ModelUsagePoint,
|
||||
type StatsModelData,
|
||||
type UsageRange,
|
||||
} from "@opencode-ai/stats-core/domain/home"
|
||||
import { runtime } from "@opencode-ai/stats-core/runtime"
|
||||
import { createAsync, query, useParams } from "@solidjs/router"
|
||||
import { createMemo, createSignal, 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"
|
||||
import { findModelCatalogEntry, formatCatalogLabName, getModelCatalog, type ModelCatalogEntry } from "../model-catalog"
|
||||
import {
|
||||
applyThemePreference,
|
||||
Footer,
|
||||
getGitHubStars,
|
||||
Header,
|
||||
isThemePreference,
|
||||
themeStorageKey,
|
||||
type HeaderLink,
|
||||
type ThemePreference,
|
||||
} from "../stats-shell"
|
||||
|
||||
const statsCanonicalBaseUrl = "https://opencode.ai/data/"
|
||||
const statsUnfurlPath = "banner.png"
|
||||
const statsUnfurlAlt = "OpenCode Data wordmark on a dark patterned background"
|
||||
const statsUnfurlUrl = new URL(statsUnfurlPath, statsCanonicalBaseUrl).toString()
|
||||
const modelHeaderLinks: readonly HeaderLink[] = [
|
||||
{ href: "#overview", label: "Overview" },
|
||||
{ href: "#usage", label: "Usage" },
|
||||
{ href: "#efficiency", label: "Efficiency" },
|
||||
{ href: "#geo-breakdown", label: "Geo Breakdown" },
|
||||
{ href: "#peers", label: "Peers" },
|
||||
]
|
||||
const modelFooterLinks: readonly HeaderLink[] = [
|
||||
{ href: import.meta.env.BASE_URL, label: "Data Home" },
|
||||
{ href: `${import.meta.env.BASE_URL}#top-models`, label: "Top Models" },
|
||||
{ href: `${import.meta.env.BASE_URL}#leaderboard`, label: "Leaderboard" },
|
||||
{ href: `${import.meta.env.BASE_URL}#session-cost`, label: "Session Cost" },
|
||||
{ href: `${import.meta.env.BASE_URL}#token-cost`, label: "Token Cost" },
|
||||
{ href: `${import.meta.env.BASE_URL}#market-share`, label: "Market Share" },
|
||||
{ href: `${import.meta.env.BASE_URL}#geo-breakdown`, label: "Geo Breakdown" },
|
||||
]
|
||||
const geoMapWidth = 960
|
||||
const geoMapHeight = 430
|
||||
const countryDisplayNames = new Intl.DisplayNames(["en"], { type: "region" })
|
||||
|
||||
type IsoCountryCode = readonly [string, string, string]
|
||||
type WorldCountryProperties = GeoJsonProperties & { name?: string }
|
||||
type WorldTopology = Topology<{ countries: GeometryCollection<WorldCountryProperties> }>
|
||||
|
||||
const countryNumericIds = new Map(
|
||||
(JSON.parse(countryCodesSource) as IsoCountryCode[]).map((country) => [country[0], country[2]] as const),
|
||||
)
|
||||
const worldTopology = JSON.parse(countriesTopologySource) as WorldTopology
|
||||
const worldCountryGeometries: GeometryCollection<WorldCountryProperties> = {
|
||||
...worldTopology.objects.countries,
|
||||
geometries: worldTopology.objects.countries.geometries.filter((country) => String(country.id ?? "") !== "010"),
|
||||
}
|
||||
const worldCountries = feature<WorldCountryProperties>(worldTopology, worldCountryGeometries) as FeatureCollection<
|
||||
GeometryObject,
|
||||
WorldCountryProperties
|
||||
>
|
||||
const worldProjection = geoEquirectangular().fitExtent(
|
||||
[
|
||||
[10, 12],
|
||||
[geoMapWidth - 10, geoMapHeight - 12],
|
||||
],
|
||||
worldCountries,
|
||||
)
|
||||
const worldPath = geoPath(worldProjection)
|
||||
const worldCountryPaths = worldCountries.features.map((country) => ({
|
||||
id: String(country.id ?? "").padStart(3, "0"),
|
||||
path: worldPath(country) ?? "",
|
||||
}))
|
||||
const worldBorderPath = worldPath(mesh(worldTopology, worldCountryGeometries, (a, b) => a !== b)) ?? ""
|
||||
|
||||
const getModelData = query(async (lab: string, model: string) => {
|
||||
"use server"
|
||||
return runtime.runPromise(getStatsModelData(model, lab))
|
||||
}, "getStatsModelData")
|
||||
|
||||
export default function StatsModel() {
|
||||
const event = getRequestEvent()
|
||||
event?.response.headers.set("Cache-Control", "public, max-age=60, s-maxage=300, stale-while-revalidate=86400")
|
||||
const params = useParams()
|
||||
const labParam = createMemo(() => params.lab ?? "")
|
||||
const modelParam = createMemo(() => params.model ?? "")
|
||||
const catalog = createAsync(() => getModelCatalog())
|
||||
const catalogEntry = createMemo(() => {
|
||||
const data = catalog()
|
||||
if (!data) return undefined
|
||||
return findModelCatalogEntry(data, modelParam(), labParam()) ?? null
|
||||
})
|
||||
const stats = createAsync(() => {
|
||||
const entry = catalogEntry()
|
||||
if (catalog() === undefined || entry === undefined) return Promise.resolve(undefined)
|
||||
if (!entry && (!labParam() || !modelParam())) return Promise.resolve(null)
|
||||
return getModelData(labParam(), entry?.slug ?? modelParam())
|
||||
})
|
||||
const githubStars = createAsync(() => getGitHubStars())
|
||||
const [themePreference, setThemePreference] = createSignal<ThemePreference>("system")
|
||||
const modelName = createMemo(() => catalogEntry()?.name ?? stats()?.model ?? modelParam() ?? "Model")
|
||||
const labName = createMemo(() => formatCatalogLabName(catalogEntry()?.lab ?? stats()?.provider ?? labParam()))
|
||||
const modelTitle = createMemo(() => `${modelName()} Data`)
|
||||
const modelDescription = createMemo(() =>
|
||||
stats()
|
||||
? `${modelName()} usage, rank, token mix, cost, geo breakdown, and peer data across OpenCode.`
|
||||
: `${modelName()} model facts, limits, and OpenCode usage availability.`,
|
||||
)
|
||||
const modelUrl = createMemo(() =>
|
||||
new URL(
|
||||
catalogEntry()?.id ?? [labParam(), stats()?.slug ?? modelParam()].filter((part) => part.length > 0).join("/"),
|
||||
statsCanonicalBaseUrl,
|
||||
).toString(),
|
||||
)
|
||||
const updateThemePreference = (preference: ThemePreference) => {
|
||||
applyThemePreference(preference)
|
||||
setThemePreference(preference)
|
||||
if (typeof window === "undefined") return
|
||||
window.localStorage.setItem(themeStorageKey, preference)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (typeof window === "undefined") return
|
||||
const preference = window.localStorage.getItem(themeStorageKey)
|
||||
const nextPreference = isThemePreference(preference) ? preference : "system"
|
||||
applyThemePreference(nextPreference)
|
||||
setThemePreference(nextPreference)
|
||||
})
|
||||
|
||||
return (
|
||||
<main data-page="stats" data-theme={themePreference()}>
|
||||
<Title>{modelTitle()}</Title>
|
||||
<Meta name="description" content={modelDescription()} />
|
||||
<Link rel="canonical" href={modelUrl()} />
|
||||
<Meta property="og:type" content="website" />
|
||||
<Meta property="og:site_name" content="OpenCode" />
|
||||
<Meta property="og:title" content={modelTitle()} />
|
||||
<Meta property="og:description" content={modelDescription()} />
|
||||
<Meta property="og:url" content={modelUrl()} />
|
||||
<Meta property="og:image" content={statsUnfurlUrl} />
|
||||
<Meta property="og:image:type" content="image/png" />
|
||||
<Meta property="og:image:width" content="1200" />
|
||||
<Meta property="og:image:height" content="630" />
|
||||
<Meta property="og:image:alt" content={statsUnfurlAlt} />
|
||||
<Meta name="twitter:card" content="summary_large_image" />
|
||||
<Meta name="twitter:title" content={modelTitle()} />
|
||||
<Meta name="twitter:description" content={modelDescription()} />
|
||||
<Meta name="twitter:image" content={statsUnfurlUrl} />
|
||||
<Meta name="twitter:image:alt" content={statsUnfurlAlt} />
|
||||
<Header githubStars={githubStars() ?? "150K"} links={modelHeaderLinks} brandHref={import.meta.env.BASE_URL} />
|
||||
<div data-component="container">
|
||||
<div data-component="content">
|
||||
<Show when={catalogEntry() || stats() !== undefined} fallback={<ModelLoading />}>
|
||||
<Show when={catalogEntry() || stats()} fallback={<ModelNotFound lab={labParam()} model={modelParam()} />}>
|
||||
<>
|
||||
<ModelHero data={stats() ?? null} catalog={catalogEntry() ?? null} labName={labName()} />
|
||||
<ModelOverview data={stats() ?? null} />
|
||||
<ModelUsageSection data={stats()?.usage ?? []} />
|
||||
<ModelEfficiencySection data={stats() ?? null} />
|
||||
<ModelGeoBreakdownSection data={stats()?.country ?? emptyCountryRecord()} />
|
||||
<ModelPeersSection data={stats() ?? null} />
|
||||
</>
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
<Footer
|
||||
themePreference={themePreference()}
|
||||
onThemePreferenceChange={updateThemePreference}
|
||||
links={modelFooterLinks}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
function ModelLoading() {
|
||||
return (
|
||||
<>
|
||||
<section id="overview" data-section="model-hero">
|
||||
<div data-slot="model-hero-grid">
|
||||
<div data-slot="model-hero-copy">
|
||||
<a data-slot="model-back-link" href={import.meta.env.BASE_URL}>
|
||||
Data
|
||||
</a>
|
||||
<h1>Model Data</h1>
|
||||
<p>Reading model aggregates from model_stat.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section data-section="model-panel">
|
||||
<ModelEmptyState title="Loading model data" description="Reading the model profile." />
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function ModelNotFound(props: { lab: string; model: string }) {
|
||||
return (
|
||||
<>
|
||||
<section id="overview" data-section="model-hero">
|
||||
<div data-slot="model-hero-grid">
|
||||
<div data-slot="model-hero-copy">
|
||||
<a data-slot="model-back-link" href={import.meta.env.BASE_URL}>
|
||||
Data
|
||||
</a>
|
||||
<h1>{props.model || "Model"}</h1>
|
||||
<p>No model facts or model_stat rows matched {props.lab ? `${props.lab}/${props.model}` : props.model}.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section data-section="model-panel">
|
||||
<ModelEmptyState title="No model data" description="Try opening a model from the leaderboard." />
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function ModelHero(props: { data: StatsModelData | null; catalog: ModelCatalogEntry | null; labName: string }) {
|
||||
const labId = () => props.catalog?.lab ?? props.data?.provider ?? props.labName
|
||||
const modelId = () => props.catalog?.id ?? props.data?.model ?? "Model"
|
||||
const weights = () => props.catalog?.weights[0]
|
||||
return (
|
||||
<section id="overview" data-section="model-hero">
|
||||
<a data-slot="model-back-link" href={import.meta.env.BASE_URL}>
|
||||
Data
|
||||
</a>
|
||||
<div data-slot="model-hero-grid">
|
||||
<div data-slot="model-hero-copy">
|
||||
<div data-slot="model-hero-tags">
|
||||
<a data-slot="hero-meta" href={`${import.meta.env.BASE_URL}${providerSlug(labId())}`}>
|
||||
<ProviderIcon aria-hidden="true" id={getProviderIconId(labId())} />
|
||||
<span>{props.labName}</span>
|
||||
</a>
|
||||
<span data-slot="model-id-tag">{modelId()}</span>
|
||||
</div>
|
||||
<h1>{props.catalog?.name ?? props.data?.model ?? "Model"}</h1>
|
||||
<Show
|
||||
when={props.data}
|
||||
fallback={
|
||||
<p>Model facts from the shared model index. OpenCode usage appears once this model has activity.</p>
|
||||
}
|
||||
>
|
||||
{(data) => (
|
||||
<p>
|
||||
Ranked #{data().rank} across recent OpenCode token usage with {formatPercent(data().tokenShare)} of
|
||||
observed volume.
|
||||
</p>
|
||||
)}
|
||||
</Show>
|
||||
<Show when={props.catalog?.openWeights && weights()}>
|
||||
{(weight) => (
|
||||
<a data-slot="model-weight-link" href={weight().url} target="_blank" rel="noopener noreferrer">
|
||||
Model weights: {weight().label}
|
||||
</a>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={props.data} fallback={<ModelCatalogCallout catalog={props.catalog} />}>
|
||||
{(data) => (
|
||||
<div data-component="model-rank-panel">
|
||||
<span>Current Rank</span>
|
||||
<strong>#{data().rank}</strong>
|
||||
<p>{formatRankMoveLabel(data().previousRank, data().rank)}</p>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
<div data-slot="model-hero-pattern" aria-hidden="true" />
|
||||
<Show when={props.catalog}>{(catalog) => <ModelCatalogPanel data={catalog()} />}</Show>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function ModelCatalogCallout(props: { catalog: ModelCatalogEntry | null }) {
|
||||
return (
|
||||
<div data-component="model-rank-panel">
|
||||
<span>Model Profile</span>
|
||||
<strong>{props.catalog?.releaseDate ? formatCatalogDate(props.catalog.releaseDate) : "Listed"}</strong>
|
||||
<p>No OpenCode usage in the current data window.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ModelCatalogPanel(props: { data: ModelCatalogEntry }) {
|
||||
return (
|
||||
<aside data-component="model-catalog" aria-label="Model facts">
|
||||
<div data-slot="model-catalog-grid">
|
||||
<CatalogDatum label="Context" value={formatCatalogLimit(props.data.limit?.context)} />
|
||||
<CatalogDatum label="Output" value={formatCatalogLimit(props.data.limit?.output)} />
|
||||
<CatalogDatum label="Knowledge" value={formatCatalogDate(props.data.knowledge)} />
|
||||
<CatalogDatum label="Release" value={formatCatalogDate(props.data.releaseDate)} />
|
||||
<CatalogDatum label="Inputs" value={formatCatalogModalities(props.data.modalities.input)} />
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
||||
function CatalogDatum(props: { label: string; value: string }) {
|
||||
return (
|
||||
<article data-component="model-catalog-datum">
|
||||
<span>{props.label}</span>
|
||||
<strong>{props.value}</strong>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
function ModelOverview(props: { data: StatsModelData | null }) {
|
||||
return (
|
||||
<section data-section="model-panel">
|
||||
<SectionTitle title="Overview" description="Recent tokens, sessions, and market position." />
|
||||
<Show
|
||||
when={props.data}
|
||||
fallback={<ModelEmptyState title="No usage summary" description="This model has no OpenCode usage rows yet." />}
|
||||
>
|
||||
{(data) => (
|
||||
<div data-component="model-metric-grid">
|
||||
<MetricCard label="Tokens" value={formatTokens(data().totals.tokens)} detail="last two months" />
|
||||
<MetricCard label="Sessions" value={formatInteger(data().totals.sessions)} detail="completed sessions" />
|
||||
<MetricCard
|
||||
label="Token Share"
|
||||
value={formatPercent(data().tokenShare)}
|
||||
detail={`${data().totalModels} models`}
|
||||
/>
|
||||
<MetricCard
|
||||
label="Momentum"
|
||||
value={formatChange(data().tokenChange)}
|
||||
detail="vs previous window"
|
||||
state={data().tokenChange < 0 ? "negative" : "positive"}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function ModelUsageSection(props: { data: ModelUsagePoint[] }) {
|
||||
const [activeIndex, setActiveIndex] = createSignal<number>()
|
||||
const max = createMemo(() => Math.max(0, ...props.data.map((item) => item.tokens)) || 1)
|
||||
const activePoint = createMemo(() => {
|
||||
const index = activeIndex()
|
||||
if (index === undefined) return undefined
|
||||
return props.data[index]
|
||||
})
|
||||
|
||||
return (
|
||||
<section id="usage" data-section="model-panel">
|
||||
<SectionTitle title="Usage" description="Daily token volume over the recent two-month window." />
|
||||
<Show
|
||||
when={props.data.some((item) => item.tokens > 0)}
|
||||
fallback={<ModelEmptyState title="No usage" description="No usage landed in the current window." />}
|
||||
>
|
||||
<div
|
||||
data-component="model-usage-chart"
|
||||
data-dense-labels={isModelUsageDense(props.data.length) ? "true" : undefined}
|
||||
role="img"
|
||||
aria-label="Daily token usage chart"
|
||||
style={{ "--model-usage-count": props.data.length } as JSX.CSSProperties}
|
||||
onPointerLeave={(event) => {
|
||||
if (event.pointerType === "touch") return
|
||||
setActiveIndex(undefined)
|
||||
}}
|
||||
>
|
||||
<div data-slot="model-usage-axis" aria-hidden="true">
|
||||
<For each={props.data}>
|
||||
{(point, index) => (
|
||||
<div
|
||||
data-active={activeIndex() === index() ? "true" : undefined}
|
||||
data-label-hidden={isModelUsageLabelHidden(index(), props.data.length) ? "true" : undefined}
|
||||
>
|
||||
<span data-slot="model-usage-label">
|
||||
<span data-slot="model-usage-total">{formatTokens(point.tokens)}</span>
|
||||
<span data-slot="model-usage-date">{point.date}</span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<div data-slot="model-usage-bars">
|
||||
<For each={props.data}>
|
||||
{(point, index) => (
|
||||
<div
|
||||
data-slot="model-usage-column"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={`${point.date} ${formatTokens(point.tokens)} tokens`}
|
||||
data-active={activeIndex() === index() ? "true" : undefined}
|
||||
data-muted={activeIndex() !== undefined && activeIndex() !== index() ? "true" : undefined}
|
||||
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-usage-bar"
|
||||
style={{ "--model-usage-fill": `${modelUsageHeight(point.tokens, max())}%` } as JSX.CSSProperties}
|
||||
/>
|
||||
<Show when={activeIndex() === index() && activePoint()}>
|
||||
{(active) => (
|
||||
<div
|
||||
data-component="chart-tooltip"
|
||||
data-placement={index() > props.data.length * 0.62 ? "left" : "right"}
|
||||
>
|
||||
<strong>{active().date}</strong>
|
||||
<span>{formatTokens(active().tokens)} tokens</span>
|
||||
<div data-slot="tooltip-divider" />
|
||||
<p>
|
||||
<span data-slot="tooltip-label">
|
||||
<i /> Daily tokens
|
||||
</span>
|
||||
<b>{formatTokens(active().tokens)}</b>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function ModelEfficiencySection(props: { data: StatsModelData | null }) {
|
||||
return (
|
||||
<section id="efficiency" data-section="model-panel">
|
||||
<SectionTitle title="Efficiency" description="Cost, cache behavior, and average session shape." />
|
||||
<Show
|
||||
when={props.data}
|
||||
fallback={
|
||||
<ModelEmptyState title="No efficiency data" description="Efficiency data appears after usage lands." />
|
||||
}
|
||||
>
|
||||
{(data) => (
|
||||
<div data-component="model-metric-grid" data-variant="dense">
|
||||
<MetricCard label="Cost" value={formatMoney(data().totals.cost)} detail="total spend" />
|
||||
<MetricCard label="Cost / 1M" value={formatMoney(data().totals.costPerMillion)} detail="all tokens" />
|
||||
<MetricCard
|
||||
label="Cost / Session"
|
||||
value={formatSessionCost(data().totals.costPerSession)}
|
||||
detail="average"
|
||||
/>
|
||||
<MetricCard
|
||||
label="Tokens / Session"
|
||||
value={formatTokens(data().totals.tokensPerSession)}
|
||||
detail="average"
|
||||
/>
|
||||
<MetricCard label="Cache Ratio" value={formatPercent(data().totals.cacheRatio)} detail="input tokens" />
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function ModelGeoBreakdownSection(props: { data: Record<UsageRange, CountryEntry[]> }) {
|
||||
const [activeCountry, setActiveCountry] = createSignal<string>()
|
||||
const data = createMemo(() => props.data["2M"])
|
||||
const countryById = createMemo(
|
||||
() =>
|
||||
new Map(
|
||||
data().flatMap((country) => {
|
||||
const id = countryNumericId(country.country)
|
||||
return id ? [[id, country] as const] : []
|
||||
}),
|
||||
),
|
||||
)
|
||||
const maxTokens = createMemo(() => Math.max(0, ...data().map((country) => country.tokens)) || 1)
|
||||
const topCountries = createMemo(() => data().slice(0, 15))
|
||||
const active = createMemo(() => data().find((country) => country.country === activeCountry()) ?? data()[0])
|
||||
|
||||
return (
|
||||
<section
|
||||
id="geo-breakdown"
|
||||
data-section="geo-breakdown"
|
||||
onPointerLeave={(event) => {
|
||||
if (event.pointerType === "touch") return
|
||||
setActiveCountry(undefined)
|
||||
}}
|
||||
>
|
||||
<SectionTitle title="Geo Breakdown" description="Model tokens used by country." />
|
||||
<Show
|
||||
when={data().length > 0}
|
||||
fallback={<ModelEmptyState title="No geo data" description="No geo_stat rows matched this model." />}
|
||||
>
|
||||
<div data-component="geo-breakdown">
|
||||
<div data-slot="geo-map-panel">
|
||||
<GeoWorldMap
|
||||
countryById={countryById()}
|
||||
activeCountry={activeCountry()}
|
||||
maxTokens={maxTokens()}
|
||||
onActiveCountryChange={setActiveCountry}
|
||||
/>
|
||||
<Show when={active()}>
|
||||
{(country) => (
|
||||
<div data-slot="geo-active-country">
|
||||
<span>#{String(country().rank).padStart(2, "0")}</span>
|
||||
<strong>{formatCountryName(country().country)}</strong>
|
||||
<p>
|
||||
<b>{formatGeoTokens(country().tokens)}</b>
|
||||
<em>{formatGeoShare(country().share)}</em>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
<GeoCountryList
|
||||
data={topCountries()}
|
||||
activeCountry={activeCountry()}
|
||||
maxTokens={maxTokens()}
|
||||
onActiveCountryChange={setActiveCountry}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function GeoWorldMap(props: {
|
||||
countryById: Map<string, CountryEntry>
|
||||
activeCountry: string | undefined
|
||||
maxTokens: number
|
||||
onActiveCountryChange: (country: string | undefined) => void
|
||||
}) {
|
||||
const opacityScale = createMemo(() => scaleSqrt().domain([0, props.maxTokens]).range([0.26, 0.96]).clamp(true))
|
||||
const countryOpacity = (country: CountryEntry | undefined) => {
|
||||
if (!country) return 0
|
||||
const opacity = opacityScale()(country.tokens)
|
||||
if (!props.activeCountry || props.activeCountry === country.country) return opacity
|
||||
return Math.max(0.18, opacity * 0.36)
|
||||
}
|
||||
|
||||
return (
|
||||
<svg
|
||||
data-component="geo-world-map"
|
||||
viewBox={`0 0 ${geoMapWidth} ${geoMapHeight}`}
|
||||
role="img"
|
||||
aria-label="World map of model token usage by country"
|
||||
>
|
||||
<title>Geo Breakdown map</title>
|
||||
<g data-slot="geo-countries">
|
||||
<For each={worldCountryPaths}>
|
||||
{(country) => {
|
||||
const entry = () => props.countryById.get(country.id)
|
||||
return (
|
||||
<path
|
||||
d={country.path}
|
||||
data-has-data={entry() ? "true" : undefined}
|
||||
data-active={entry()?.country === props.activeCountry ? "true" : undefined}
|
||||
style={{ "--geo-country-opacity": String(countryOpacity(entry())) } as JSX.CSSProperties}
|
||||
aria-hidden="true"
|
||||
onPointerEnter={() => {
|
||||
const item = entry()
|
||||
if (!item) return
|
||||
props.onActiveCountryChange(item.country)
|
||||
}}
|
||||
onClick={() => {
|
||||
const item = entry()
|
||||
if (!item) return
|
||||
props.onActiveCountryChange(item.country)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</g>
|
||||
<path data-slot="geo-borders" d={worldBorderPath} aria-hidden="true" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function GeoCountryList(props: {
|
||||
data: CountryEntry[]
|
||||
activeCountry: string | undefined
|
||||
maxTokens: number
|
||||
onActiveCountryChange: (country: string | undefined) => void
|
||||
}) {
|
||||
const opacityScale = createMemo(() => scaleSqrt().domain([0, props.maxTokens]).range([0.26, 0.96]).clamp(true))
|
||||
|
||||
return (
|
||||
<ol data-component="geo-country-list">
|
||||
<For each={props.data}>
|
||||
{(country) => (
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
data-active={props.activeCountry === country.country ? "true" : undefined}
|
||||
style={{ "--geo-row-opacity": String(opacityScale()(country.tokens)) } as JSX.CSSProperties}
|
||||
aria-label={`${formatCountryName(country.country)} ${formatGeoTokens(country.tokens)} ${formatGeoShare(
|
||||
country.share,
|
||||
)}`}
|
||||
onClick={() => props.onActiveCountryChange(country.country)}
|
||||
onPointerEnter={() => props.onActiveCountryChange(country.country)}
|
||||
onFocus={() => props.onActiveCountryChange(country.country)}
|
||||
>
|
||||
<span>{String(country.rank).padStart(2, "0")}</span>
|
||||
<i />
|
||||
<strong>{formatCountryName(country.country)}</strong>
|
||||
<em>{formatGeoTokens(country.tokens)}</em>
|
||||
<b>{formatGeoShare(country.share)}</b>
|
||||
</button>
|
||||
</li>
|
||||
)}
|
||||
</For>
|
||||
</ol>
|
||||
)
|
||||
}
|
||||
|
||||
function ModelPeersSection(props: { data: StatsModelData | null }) {
|
||||
return (
|
||||
<section id="peers" data-section="model-panel">
|
||||
<SectionTitle title="Peers" description="Nearby models by recent token volume." />
|
||||
<Show
|
||||
when={props.data?.peers.length}
|
||||
fallback={<ModelEmptyState title="No peers" description="Peer rankings appear after usage lands." />}
|
||||
>
|
||||
<ol data-component="model-peer-list">
|
||||
<For each={props.data?.peers ?? []}>
|
||||
{(peer) => <PeerRow peer={peer} active={peer.model === props.data?.model} />}
|
||||
</For>
|
||||
</ol>
|
||||
</Show>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function MetricCard(props: { label: string; value: string; detail: string; state?: "positive" | "negative" }) {
|
||||
return (
|
||||
<article data-component="model-metric" data-state={props.state}>
|
||||
<span>{props.label}</span>
|
||||
<strong>{props.value}</strong>
|
||||
<p>{props.detail}</p>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
function PeerRow(props: { peer: ModelPeerEntry; active: boolean }) {
|
||||
return (
|
||||
<li>
|
||||
<a
|
||||
href={`${import.meta.env.BASE_URL}${providerSlug(props.peer.provider)}/${props.peer.slug}`}
|
||||
data-active={props.active ? "true" : undefined}
|
||||
>
|
||||
<span>{String(props.peer.rank).padStart(2, "0")}</span>
|
||||
<ProviderIcon aria-hidden="true" id={getProviderIconId(props.peer.author)} />
|
||||
<strong>{props.peer.model}</strong>
|
||||
<em>{props.peer.author}</em>
|
||||
<b>{formatTokens(props.peer.tokens)}</b>
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
function SectionTitle(props: { title: string; description: string }) {
|
||||
return (
|
||||
<p data-slot="section-title">
|
||||
<strong>{props.title}.</strong> <span>{props.description}</span>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
function ModelEmptyState(props: { title: string; description: string; compact?: boolean }) {
|
||||
return (
|
||||
<div data-component="empty-state" data-compact={props.compact ? "true" : undefined}>
|
||||
<strong>{props.title}</strong>
|
||||
<p>{props.description}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function getProviderIconId(author: string) {
|
||||
if (author === "MiniMax") return "minimax"
|
||||
if (author === "Moonshot") return "moonshotai"
|
||||
if (author === "Zhipu") return "zhipuai"
|
||||
return author.toLowerCase().replace(/[^a-z0-9]+/g, "")
|
||||
}
|
||||
|
||||
function emptyCountryRecord(): Record<UsageRange, CountryEntry[]> {
|
||||
return {
|
||||
"1D": [],
|
||||
"1W": [],
|
||||
"2W": [],
|
||||
"1M": [],
|
||||
"2M": [],
|
||||
"3M": [],
|
||||
YTD: [],
|
||||
ALL: [],
|
||||
}
|
||||
}
|
||||
|
||||
function countryNumericId(country: string) {
|
||||
return countryNumericIds.get(country.toUpperCase())?.padStart(3, "0")
|
||||
}
|
||||
|
||||
function formatCountryName(country: string) {
|
||||
const code = country.toUpperCase()
|
||||
if (code === "ZZ") return "Unknown"
|
||||
if (!countryNumericId(code)) return code
|
||||
return countryDisplayNames.of(code) ?? code
|
||||
}
|
||||
|
||||
function formatGeoTokens(value: number) {
|
||||
return formatTokens(value * 1_000_000_000_000)
|
||||
}
|
||||
|
||||
function formatGeoShare(value: number) {
|
||||
return `${value.toFixed(value > 0 && value < 1 ? 1 : 0)}%`
|
||||
}
|
||||
|
||||
function modelUsageHeight(tokens: number, max: number) {
|
||||
if (tokens <= 0) return 0
|
||||
return Math.max(2, Math.min(100, (tokens / max) * 100))
|
||||
}
|
||||
|
||||
function isModelUsageDense(count: number) {
|
||||
return count > 20
|
||||
}
|
||||
|
||||
function isModelUsageLabelHidden(index: number, count: number) {
|
||||
if (count <= 16) return false
|
||||
const interval = Math.ceil(count / 8)
|
||||
return index !== count - 1 && index % interval !== 0
|
||||
}
|
||||
|
||||
function formatRankMove(previousRank: number, rank: number) {
|
||||
const change = previousRank - rank
|
||||
if (change > 0) return `+${change}`
|
||||
if (change < 0) return `${change}`
|
||||
return "Even"
|
||||
}
|
||||
|
||||
function formatRankMoveLabel(previousRank: number | null, rank: number) {
|
||||
return previousRank === null ? "New in window" : `${formatRankMove(previousRank, rank)} vs previous window`
|
||||
}
|
||||
|
||||
function formatTokens(value: number) {
|
||||
if (value >= 1_000_000_000_000)
|
||||
return `${trimNumber(value / 1_000_000_000_000, value >= 10_000_000_000_000 ? 0 : 1)}T`
|
||||
if (value >= 1_000_000_000) return `${trimNumber(value / 1_000_000_000, value >= 10_000_000_000 ? 0 : 1)}B`
|
||||
if (value >= 1_000_000) return `${trimNumber(value / 1_000_000, value >= 10_000_000 ? 0 : 1)}M`
|
||||
if (value >= 1_000) return `${trimNumber(value / 1_000, value >= 10_000 ? 0 : 1)}K`
|
||||
return String(Math.round(value))
|
||||
}
|
||||
|
||||
function formatInteger(value: number) {
|
||||
return new Intl.NumberFormat("en").format(value)
|
||||
}
|
||||
|
||||
function formatPercent(value: number) {
|
||||
return `${value.toFixed(value > 0 && value < 10 ? 1 : 0)}%`
|
||||
}
|
||||
|
||||
function formatMoney(value: number) {
|
||||
if (value >= 1_000_000) return `$${trimNumber(value / 1_000_000, value >= 10_000_000 ? 0 : 1)}M`
|
||||
if (value >= 1_000) return `$${trimNumber(value / 1_000, value >= 10_000 ? 0 : 1)}K`
|
||||
return `$${value.toFixed(value >= 10 ? 0 : 2)}`
|
||||
}
|
||||
|
||||
function formatSessionCost(value: number) {
|
||||
return `$${value.toFixed(value > 0 && value < 0.01 ? 4 : 2)}`
|
||||
}
|
||||
|
||||
function formatChange(value: number) {
|
||||
if (value > 0) return `+${value}%`
|
||||
return `${value}%`
|
||||
}
|
||||
|
||||
function formatCatalogLimit(value: number | undefined) {
|
||||
return value === undefined ? "Unknown" : formatTokens(value)
|
||||
}
|
||||
|
||||
function formatCatalogModalities(value: string[]) {
|
||||
if (value.length === 0) return "Unknown"
|
||||
return value.map(formatCatalogModality).join(", ")
|
||||
}
|
||||
|
||||
function formatCatalogModality(value: string) {
|
||||
if (value === "pdf") return "PDF"
|
||||
return value.charAt(0).toUpperCase() + value.slice(1)
|
||||
}
|
||||
|
||||
function formatCatalogDate(value: string | undefined) {
|
||||
if (!value) return "Unknown"
|
||||
const match = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/.exec(value)
|
||||
if (!match) return value
|
||||
const year = Number(match[1])
|
||||
const month = match[2] ? Number(match[2]) - 1 : 0
|
||||
const day = match[3] ? Number(match[3]) : 1
|
||||
return new Intl.DateTimeFormat("en", {
|
||||
month: match[2] ? "short" : undefined,
|
||||
day: match[3] ? "numeric" : undefined,
|
||||
year: "numeric",
|
||||
timeZone: "UTC",
|
||||
}).format(new Date(Date.UTC(year, month, day)))
|
||||
}
|
||||
|
||||
function trimNumber(value: number, digits: number) {
|
||||
return Number(value.toFixed(digits)).toLocaleString("en")
|
||||
}
|
||||
|
||||
function providerSlug(provider: string) {
|
||||
return provider
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "")
|
||||
.replace(/-{2,}/g, "-")
|
||||
}
|
||||
432
packages/stats/app/src/routes/[lab]/index.tsx
Normal file
432
packages/stats/app/src/routes/[lab]/index.tsx
Normal file
@@ -0,0 +1,432 @@
|
||||
import "../index.css"
|
||||
import { Link, Meta, Title } from "@solidjs/meta"
|
||||
import {
|
||||
getStatsLabData,
|
||||
type LabUsageModelEntry,
|
||||
type ModelUsagePoint,
|
||||
type StatsLabData,
|
||||
} from "@opencode-ai/stats-core/domain/home"
|
||||
import { runtime } from "@opencode-ai/stats-core/runtime"
|
||||
import { createAsync, query, useParams } from "@solidjs/router"
|
||||
import { createMemo, createSignal, For, onMount, Show, type JSX } from "solid-js"
|
||||
import { getRequestEvent } from "solid-js/web"
|
||||
import {
|
||||
findModelCatalogLab,
|
||||
formatCatalogLabName,
|
||||
getModelCatalog,
|
||||
type ModelCatalogEntry,
|
||||
type ModelCatalogLab,
|
||||
} from "../model-catalog"
|
||||
import {
|
||||
applyThemePreference,
|
||||
Footer,
|
||||
getGitHubStars,
|
||||
Header,
|
||||
isThemePreference,
|
||||
themeStorageKey,
|
||||
type HeaderLink,
|
||||
type ThemePreference,
|
||||
} from "../stats-shell"
|
||||
|
||||
const statsCanonicalBaseUrl = "https://opencode.ai/data/"
|
||||
const statsUnfurlPath = "banner.png"
|
||||
const statsUnfurlAlt = "OpenCode Data wordmark on a dark patterned background"
|
||||
const statsUnfurlUrl = new URL(statsUnfurlPath, statsCanonicalBaseUrl).toString()
|
||||
const labHeaderLinks: readonly HeaderLink[] = [
|
||||
{ href: "#overview", label: "Overview" },
|
||||
{ href: "#usage", label: "Usage" },
|
||||
{ href: "#models", label: "Models" },
|
||||
]
|
||||
const labFooterLinks: readonly HeaderLink[] = [
|
||||
{ href: import.meta.env.BASE_URL, label: "Data Home" },
|
||||
{ href: `${import.meta.env.BASE_URL}#top-models`, label: "Top Models" },
|
||||
{ href: `${import.meta.env.BASE_URL}#market-share`, label: "Market Share" },
|
||||
{ href: `${import.meta.env.BASE_URL}#geo-breakdown`, label: "Geo Breakdown" },
|
||||
]
|
||||
|
||||
const getLabData = query(async (lab: string) => {
|
||||
"use server"
|
||||
return runtime.runPromise(getStatsLabData(lab))
|
||||
}, "getStatsLabData")
|
||||
|
||||
export default function StatsLab() {
|
||||
const event = getRequestEvent()
|
||||
event?.response.headers.set("Cache-Control", "public, max-age=60, s-maxage=300, stale-while-revalidate=86400")
|
||||
const params = useParams()
|
||||
const labParam = createMemo(() => params.lab ?? "")
|
||||
const catalog = createAsync(() => getModelCatalog())
|
||||
const lab = createMemo(() => {
|
||||
const data = catalog()
|
||||
if (!data) return undefined
|
||||
return findModelCatalogLab(data, labParam()) ?? null
|
||||
})
|
||||
const stats = createAsync(() => {
|
||||
const entry = lab()
|
||||
if (catalog() === undefined || entry === undefined) return Promise.resolve(undefined)
|
||||
if (!entry) return Promise.resolve(null)
|
||||
return getLabData(entry.id)
|
||||
})
|
||||
const githubStars = createAsync(() => getGitHubStars())
|
||||
const [themePreference, setThemePreference] = createSignal<ThemePreference>("system")
|
||||
const labName = createMemo(() => lab()?.name ?? formatCatalogLabName(labParam()))
|
||||
const labTitle = createMemo(() => `${labName()} Models`)
|
||||
const labDescription = createMemo(
|
||||
() =>
|
||||
`Explore ${labName()} models used in OpenCode, with recent token usage, context windows, release dates, and model-specific data.`,
|
||||
)
|
||||
const labUrl = createMemo(() => new URL(lab()?.id ?? labParam(), statsCanonicalBaseUrl).toString())
|
||||
const updateThemePreference = (preference: ThemePreference) => {
|
||||
applyThemePreference(preference)
|
||||
setThemePreference(preference)
|
||||
if (typeof window === "undefined") return
|
||||
window.localStorage.setItem(themeStorageKey, preference)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (typeof window === "undefined") return
|
||||
const preference = window.localStorage.getItem(themeStorageKey)
|
||||
const nextPreference = isThemePreference(preference) ? preference : "system"
|
||||
applyThemePreference(nextPreference)
|
||||
setThemePreference(nextPreference)
|
||||
})
|
||||
|
||||
return (
|
||||
<main data-page="stats" data-theme={themePreference()}>
|
||||
<Title>{labTitle()}</Title>
|
||||
<Meta name="description" content={labDescription()} />
|
||||
<Link rel="canonical" href={labUrl()} />
|
||||
<Meta property="og:type" content="website" />
|
||||
<Meta property="og:site_name" content="OpenCode" />
|
||||
<Meta property="og:title" content={labTitle()} />
|
||||
<Meta property="og:description" content={labDescription()} />
|
||||
<Meta property="og:url" content={labUrl()} />
|
||||
<Meta property="og:image" content={statsUnfurlUrl} />
|
||||
<Meta property="og:image:type" content="image/png" />
|
||||
<Meta property="og:image:width" content="1200" />
|
||||
<Meta property="og:image:height" content="630" />
|
||||
<Meta property="og:image:alt" content={statsUnfurlAlt} />
|
||||
<Meta name="twitter:card" content="summary_large_image" />
|
||||
<Meta name="twitter:title" content={labTitle()} />
|
||||
<Meta name="twitter:description" content={labDescription()} />
|
||||
<Meta name="twitter:image" content={statsUnfurlUrl} />
|
||||
<Meta name="twitter:image:alt" content={statsUnfurlAlt} />
|
||||
<Header githubStars={githubStars() ?? "150K"} links={labHeaderLinks} brandHref={import.meta.env.BASE_URL} />
|
||||
<div data-component="container">
|
||||
<div data-component="content">
|
||||
<Show when={catalog() !== undefined} fallback={<LabLoading />}>
|
||||
<Show when={lab()} fallback={<LabNotFound lab={labParam()} />}>
|
||||
{(data) => (
|
||||
<>
|
||||
<LabHero lab={data()} stats={stats() ?? null} />
|
||||
<LabUsageSection lab={data()} data={stats() ?? null} />
|
||||
<LabModelsSection lab={data()} usage={stats()?.models ?? []} />
|
||||
</>
|
||||
)}
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
<Footer
|
||||
themePreference={themePreference()}
|
||||
onThemePreferenceChange={updateThemePreference}
|
||||
links={labFooterLinks}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
function LabLoading() {
|
||||
return (
|
||||
<section id="overview" data-section="lab-hero">
|
||||
<div data-slot="model-hero-grid">
|
||||
<div data-slot="model-hero-copy">
|
||||
<a data-slot="model-back-link" href={import.meta.env.BASE_URL}>
|
||||
Data
|
||||
</a>
|
||||
<h1>Model Lab</h1>
|
||||
<p>Reading model availability and recent OpenCode usage.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function LabNotFound(props: { lab: string }) {
|
||||
return (
|
||||
<section id="overview" data-section="lab-hero">
|
||||
<div data-slot="model-hero-grid">
|
||||
<div data-slot="model-hero-copy">
|
||||
<a data-slot="model-back-link" href={import.meta.env.BASE_URL}>
|
||||
Data
|
||||
</a>
|
||||
<h1>{formatCatalogLabName(props.lab)}</h1>
|
||||
<p>No models matched this lab.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function LabHero(props: { lab: ModelCatalogLab; stats: StatsLabData | null }) {
|
||||
const latest = createMemo(
|
||||
() =>
|
||||
props.lab.models
|
||||
.map((model) => model.releaseDate)
|
||||
.filter((value): value is string => value !== undefined)
|
||||
.toSorted((a, b) => new Date(b).getTime() - new Date(a).getTime())[0],
|
||||
)
|
||||
const featuredModels = createMemo(() => props.lab.models.slice(0, 3).map((model) => model.name))
|
||||
|
||||
return (
|
||||
<section id="overview" data-section="lab-hero">
|
||||
<a data-slot="model-back-link" href={import.meta.env.BASE_URL}>
|
||||
Data
|
||||
</a>
|
||||
<div data-slot="model-hero-grid">
|
||||
<div data-slot="model-hero-copy">
|
||||
<h1>{props.lab.name}</h1>
|
||||
<div data-slot="model-hero-pattern" aria-hidden="true" />
|
||||
<p>
|
||||
Explore {props.lab.models.length} {props.lab.name} models used in OpenCode
|
||||
<Show when={featuredModels().length > 0}> including {formatList(featuredModels())}</Show>. Compare recent
|
||||
token usage, context windows, release dates, and model-specific data.
|
||||
</p>
|
||||
</div>
|
||||
<div data-component="model-rank-panel">
|
||||
<span>Tokens Processed</span>
|
||||
<strong>{props.stats ? formatTokens(props.stats.totals.tokens) : "Pending"}</strong>
|
||||
<p>
|
||||
{props.stats
|
||||
? `${formatPercent(props.stats.tokenShare)} of recent OpenCode usage`
|
||||
: latest()
|
||||
? `Latest release ${formatCatalogDate(latest())}`
|
||||
: "Usage appears after model activity lands"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function LabUsageSection(props: { lab: ModelCatalogLab; data: StatsLabData | null }) {
|
||||
const [activeIndex, setActiveIndex] = createSignal<number>()
|
||||
const usage = createMemo(() => props.data?.usage ?? [])
|
||||
const max = createMemo(() => Math.max(0, ...usage().map((item) => item.tokens)) || 1)
|
||||
const activePoint = createMemo(() => {
|
||||
const index = activeIndex()
|
||||
if (index === undefined) return undefined
|
||||
return usage()[index]
|
||||
})
|
||||
|
||||
return (
|
||||
<section id="usage" data-section="model-panel">
|
||||
<p data-slot="section-title">
|
||||
<strong>{props.lab.name} token usage.</strong>{" "}
|
||||
<span>Daily OpenCode token volume over the last two months.</span>
|
||||
</p>
|
||||
<Show
|
||||
when={usage().some((item) => item.tokens > 0)}
|
||||
fallback={
|
||||
<LabEmptyState
|
||||
title="No usage yet"
|
||||
description="Recent token usage appears here once this lab has activity."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div
|
||||
data-component="model-usage-chart"
|
||||
data-dense-labels={isLabUsageDense(usage().length) ? "true" : undefined}
|
||||
role="img"
|
||||
aria-label={`${props.lab.name} daily token usage chart`}
|
||||
style={{ "--model-usage-count": usage().length } as JSX.CSSProperties}
|
||||
onPointerLeave={(event) => {
|
||||
if (event.pointerType === "touch") return
|
||||
setActiveIndex(undefined)
|
||||
}}
|
||||
>
|
||||
<div data-slot="model-usage-axis" aria-hidden="true">
|
||||
<For each={usage()}>
|
||||
{(point, index) => (
|
||||
<div
|
||||
data-active={activeIndex() === index() ? "true" : undefined}
|
||||
data-label-hidden={isLabUsageLabelHidden(index(), usage().length) ? "true" : undefined}
|
||||
>
|
||||
<span data-slot="model-usage-label">
|
||||
<span data-slot="model-usage-total">{formatTokens(point.tokens)}</span>
|
||||
<span data-slot="model-usage-date">{point.date}</span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<div data-slot="model-usage-bars">
|
||||
<For each={usage()}>
|
||||
{(point, index) => (
|
||||
<div
|
||||
data-slot="model-usage-column"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={`${point.date} ${formatTokens(point.tokens)} tokens`}
|
||||
data-active={activeIndex() === index() ? "true" : undefined}
|
||||
data-muted={activeIndex() !== undefined && activeIndex() !== index() ? "true" : undefined}
|
||||
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-usage-bar"
|
||||
style={{ "--model-usage-fill": `${usageHeight(point.tokens, max())}%` } as JSX.CSSProperties}
|
||||
/>
|
||||
<Show when={activeIndex() === index() && activePoint()}>
|
||||
{(active) => (
|
||||
<div
|
||||
data-component="chart-tooltip"
|
||||
data-placement={index() > usage().length * 0.62 ? "left" : "right"}
|
||||
>
|
||||
<strong>{active().date}</strong>
|
||||
<span>{formatTokens(active().tokens)} tokens</span>
|
||||
<div data-slot="tooltip-divider" />
|
||||
<p>
|
||||
<span data-slot="tooltip-label">
|
||||
<i /> Daily tokens
|
||||
</span>
|
||||
<b>{formatTokens(active().tokens)}</b>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function LabModelsSection(props: { lab: ModelCatalogLab; usage: LabUsageModelEntry[] }) {
|
||||
const usageBySlug = createMemo(() => new Map(props.usage.map((item) => [item.slug, item])))
|
||||
return (
|
||||
<section id="models" data-section="model-panel">
|
||||
<p data-slot="section-title">
|
||||
<strong>{props.lab.name} models.</strong> <span>Recent usage and limits.</span>
|
||||
</p>
|
||||
<div data-component="lab-model-grid">
|
||||
<For each={props.lab.models}>
|
||||
{(model) => <LabModelCard model={model} usage={usageBySlug().get(model.slug)} />}
|
||||
</For>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function LabModelCard(props: { model: ModelCatalogEntry; usage: LabUsageModelEntry | undefined }) {
|
||||
return (
|
||||
<a data-component="lab-model-card" href={`${import.meta.env.BASE_URL}${props.model.id}`}>
|
||||
<strong>{props.model.name}</strong>
|
||||
<div data-slot="lab-model-card-meta">
|
||||
<p>
|
||||
<b>Usage</b>
|
||||
<em>{props.usage ? formatTokens(props.usage.tokens) : "—"}</em>
|
||||
</p>
|
||||
<p>
|
||||
<b>Share</b>
|
||||
<em>{props.usage ? formatPercent(props.usage.share) : "—"}</em>
|
||||
</p>
|
||||
<p>
|
||||
<b>Context</b>
|
||||
<em>{formatCatalogLimit(props.model.limit?.context)}</em>
|
||||
</p>
|
||||
<p>
|
||||
<b>Output</b>
|
||||
<em>{formatCatalogLimit(props.model.limit?.output)}</em>
|
||||
</p>
|
||||
<p>
|
||||
<b>Release</b>
|
||||
<em>{formatCatalogDate(props.model.releaseDate)}</em>
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
function LabEmptyState(props: { title: string; description: string }) {
|
||||
return (
|
||||
<div data-component="empty-state" data-compact="true">
|
||||
<strong>{props.title}</strong>
|
||||
<p>{props.description}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function formatCatalogLimit(value: number | undefined) {
|
||||
return value === undefined ? "Unknown" : formatTokens(value)
|
||||
}
|
||||
|
||||
function formatCatalogDate(value: string | undefined) {
|
||||
if (!value) return "Unknown"
|
||||
const match = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/.exec(value)
|
||||
if (!match) return value
|
||||
const year = Number(match[1])
|
||||
const month = match[2] ? Number(match[2]) - 1 : 0
|
||||
const day = match[3] ? Number(match[3]) : 1
|
||||
return new Intl.DateTimeFormat("en", {
|
||||
month: match[2] ? "short" : undefined,
|
||||
day: match[3] ? "numeric" : undefined,
|
||||
year: "numeric",
|
||||
timeZone: "UTC",
|
||||
}).format(new Date(Date.UTC(year, month, day)))
|
||||
}
|
||||
|
||||
function formatList(values: string[]) {
|
||||
if (values.length <= 1) return values[0] ?? ""
|
||||
if (values.length === 2) return `${values[0]} and ${values[1]}`
|
||||
return `${values.slice(0, -1).join(", ")}, and ${values[values.length - 1]}`
|
||||
}
|
||||
|
||||
function formatPercent(value: number) {
|
||||
return `${trimNumber(value, value >= 10 ? 1 : 2)}%`
|
||||
}
|
||||
|
||||
function formatTokens(value: number) {
|
||||
if (value >= 1_000_000_000_000)
|
||||
return `${trimNumber(value / 1_000_000_000_000, value >= 10_000_000_000_000 ? 0 : 1)}T`
|
||||
if (value >= 1_000_000_000) return `${trimNumber(value / 1_000_000_000, value >= 10_000_000_000 ? 0 : 1)}B`
|
||||
if (value >= 1_000_000) return `${trimNumber(value / 1_000_000, value >= 10_000_000 ? 0 : 1)}M`
|
||||
if (value >= 1_000) return `${trimNumber(value / 1_000, value >= 10_000 ? 0 : 1)}K`
|
||||
return String(Math.round(value))
|
||||
}
|
||||
|
||||
function trimNumber(value: number, digits: number) {
|
||||
return Number(value.toFixed(digits)).toLocaleString("en")
|
||||
}
|
||||
|
||||
function usageHeight(value: number, max: number) {
|
||||
if (value <= 0 || max <= 0) return 0
|
||||
return Math.max(4, (value / max) * 100)
|
||||
}
|
||||
|
||||
function isLabUsageDense(count: number) {
|
||||
return count > 20
|
||||
}
|
||||
|
||||
function isLabUsageLabelHidden(index: number, count: number) {
|
||||
if (count <= 14) return false
|
||||
const cadence = count > 45 ? 7 : count > 28 ? 4 : 2
|
||||
return index % cadence !== 0 && index !== count - 1
|
||||
}
|
||||
19
packages/stats/app/src/routes/api/health.ts
Normal file
19
packages/stats/app/src/routes/api/health.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { AppConfig } from "@opencode-ai/stats-core/config"
|
||||
import { runtime } from "@opencode-ai/stats-core/runtime"
|
||||
import { Effect } from "effect"
|
||||
|
||||
export async function GET() {
|
||||
return Response.json(
|
||||
await runtime.runPromise(
|
||||
Effect.gen(function* () {
|
||||
const config = yield* AppConfig
|
||||
return {
|
||||
ok: true,
|
||||
app: "stats",
|
||||
stage: config.stage,
|
||||
publicUrl: config.publicUrl,
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
29
packages/stats/app/src/routes/api/newsletter.ts
Normal file
29
packages/stats/app/src/routes/api/newsletter.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Resource } from "sst/resource"
|
||||
|
||||
const listId = "8b9bb82c-9d5f-11f0-975f-0df6fd1e4945"
|
||||
|
||||
export async function POST(event: { request: Request }) {
|
||||
const contentType = event.request.headers.get("content-type") ?? ""
|
||||
if (!contentType.includes("multipart/form-data") && !contentType.includes("application/x-www-form-urlencoded")) {
|
||||
return Response.json({ error: "Email address is required" }, { status: 400 })
|
||||
}
|
||||
|
||||
const form = await event.request.formData()
|
||||
const emailAddress = form.get("email")
|
||||
if (typeof emailAddress !== "string" || emailAddress.trim().length === 0) {
|
||||
return Response.json({ error: "Email address is required" }, { status: 400 })
|
||||
}
|
||||
|
||||
const response = await fetch(`https://api.emailoctopus.com/lists/${listId}/contacts`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
Authorization: `Bearer ${Resource.EMAILOCTOPUS_API_KEY.value}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email_address: emailAddress.trim(),
|
||||
}),
|
||||
})
|
||||
if (!response.ok) return Response.json({ error: "Failed to subscribe" }, { status: 502 })
|
||||
return Response.json({ success: true })
|
||||
}
|
||||
4041
packages/stats/app/src/routes/index.css
Normal file
4041
packages/stats/app/src/routes/index.css
Normal file
File diff suppressed because it is too large
Load Diff
1774
packages/stats/app/src/routes/index.tsx
Normal file
1774
packages/stats/app/src/routes/index.tsx
Normal file
File diff suppressed because it is too large
Load Diff
224
packages/stats/app/src/routes/model-catalog.ts
Normal file
224
packages/stats/app/src/routes/model-catalog.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
import { query } from "@solidjs/router"
|
||||
|
||||
export const modelCatalogSourceUrl = "https://models.dev/models.json"
|
||||
|
||||
export type ModelCatalogEntry = {
|
||||
id: string
|
||||
lab: string
|
||||
slug: string
|
||||
name: string
|
||||
family?: string
|
||||
knowledge?: string
|
||||
releaseDate?: string
|
||||
lastUpdated?: string
|
||||
limit?: { context?: number; output?: number }
|
||||
modalities: { input: string[]; output: string[] }
|
||||
openWeights: boolean
|
||||
reasoning: boolean
|
||||
toolCall: boolean
|
||||
attachment: boolean
|
||||
temperature: boolean
|
||||
weights: { label: string; url: string }[]
|
||||
benchmarks: ModelCatalogBenchmark[]
|
||||
}
|
||||
|
||||
export type ModelCatalogBenchmark = {
|
||||
name: string
|
||||
score: number
|
||||
metric?: string
|
||||
harness?: string
|
||||
variant?: string
|
||||
dataset?: string
|
||||
version?: string
|
||||
source?: string
|
||||
}
|
||||
|
||||
export type ModelCatalogLab = {
|
||||
id: string
|
||||
name: string
|
||||
models: ModelCatalogEntry[]
|
||||
}
|
||||
|
||||
export type ModelCatalog = {
|
||||
models: ModelCatalogEntry[]
|
||||
labs: ModelCatalogLab[]
|
||||
}
|
||||
|
||||
export const getModelCatalog = query(async () => {
|
||||
"use server"
|
||||
const payload = await fetch(modelCatalogSourceUrl)
|
||||
.then((response): Promise<unknown> => (response.ok ? (response.json() as Promise<unknown>) : Promise.resolve()))
|
||||
.catch(() => undefined)
|
||||
return buildModelCatalog(payload)
|
||||
}, "getModelCatalog")
|
||||
|
||||
export function findModelCatalogEntry(catalog: ModelCatalog, model: string, lab?: string) {
|
||||
const normalizedId = lab ? `${catalogSlug(lab)}/${catalogSlug(model)}` : model.trim().toLowerCase()
|
||||
const leaf = catalogSlug(model)
|
||||
return (
|
||||
catalog.models.find((entry) => entry.id.toLowerCase() === normalizedId) ??
|
||||
catalog.models.find((entry) => (lab ? entry.lab === catalogSlug(lab) : true) && entry.slug === leaf) ??
|
||||
catalog.models.find((entry) => entry.slug === leaf)
|
||||
)
|
||||
}
|
||||
|
||||
export function findModelCatalogLab(catalog: ModelCatalog, lab: string) {
|
||||
const id = catalogSlug(lab)
|
||||
return catalog.labs.find((entry) => entry.id === id)
|
||||
}
|
||||
|
||||
export function formatCatalogLabName(lab: string) {
|
||||
const known: Record<string, string> = {
|
||||
alibaba: "Alibaba",
|
||||
anthropic: "Anthropic",
|
||||
cohere: "Cohere",
|
||||
deepseek: "DeepSeek",
|
||||
google: "Google",
|
||||
meta: "Meta",
|
||||
minimax: "MiniMax",
|
||||
mistral: "Mistral",
|
||||
moonshotai: "Moonshot",
|
||||
openai: "OpenAI",
|
||||
perplexity: "Perplexity",
|
||||
stepfun: "StepFun",
|
||||
tencent: "Tencent",
|
||||
xai: "xAI",
|
||||
xiaomi: "Xiaomi",
|
||||
zai: "Z.ai",
|
||||
zhipuai: "Zhipu",
|
||||
}
|
||||
return known[catalogSlug(lab)] ?? lab.replace(/[-_]/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase())
|
||||
}
|
||||
|
||||
export function catalogSlug(value: string) {
|
||||
return value
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "")
|
||||
.replace(/-{2,}/g, "-")
|
||||
}
|
||||
|
||||
function buildModelCatalog(payload: unknown): ModelCatalog {
|
||||
const models = (Array.isArray(payload) ? payload : isRecord(payload) ? Object.values(payload) : [])
|
||||
.flatMap(readModelCatalogEntry)
|
||||
.toSorted((a, b) => a.lab.localeCompare(b.lab) || displayDateTime(b.releaseDate) - displayDateTime(a.releaseDate))
|
||||
return {
|
||||
models,
|
||||
labs: Object.values(
|
||||
models.reduce<Record<string, ModelCatalogLab>>((result, model) => {
|
||||
result[model.lab] = {
|
||||
id: model.lab,
|
||||
name: formatCatalogLabName(model.lab),
|
||||
models: [...(result[model.lab]?.models ?? []), model],
|
||||
}
|
||||
return result
|
||||
}, {}),
|
||||
).toSorted((a, b) => a.name.localeCompare(b.name)),
|
||||
}
|
||||
}
|
||||
|
||||
function readModelCatalogEntry(value: unknown): ModelCatalogEntry[] {
|
||||
if (!isRecord(value)) return []
|
||||
const id = stringValue(value.id)
|
||||
const name = stringValue(value.name)
|
||||
const lab = id?.split("/")[0]
|
||||
const slug = id?.split("/").slice(1).join("/")
|
||||
if (!id || !name || !lab || !slug) return []
|
||||
return [
|
||||
{
|
||||
id,
|
||||
lab: catalogSlug(lab),
|
||||
slug: catalogSlug(slug),
|
||||
name,
|
||||
family: stringValue(value.family),
|
||||
knowledge: stringValue(value.knowledge),
|
||||
releaseDate: stringValue(value.release_date),
|
||||
lastUpdated: stringValue(value.last_updated),
|
||||
limit: readCatalogLimit(value.limit),
|
||||
modalities: readCatalogModalities(value.modalities),
|
||||
openWeights: booleanValue(value.open_weights),
|
||||
reasoning: booleanValue(value.reasoning),
|
||||
toolCall: booleanValue(value.tool_call),
|
||||
attachment: booleanValue(value.attachment),
|
||||
temperature: booleanValue(value.temperature),
|
||||
weights: readCatalogWeights(value.weights),
|
||||
benchmarks: readCatalogBenchmarks(value.benchmarks),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
function readCatalogLimit(value: unknown) {
|
||||
if (!isRecord(value)) return undefined
|
||||
return {
|
||||
context: numberValue(value.context),
|
||||
output: numberValue(value.output),
|
||||
}
|
||||
}
|
||||
|
||||
function readCatalogModalities(value: unknown) {
|
||||
if (!isRecord(value)) return { input: [], output: [] }
|
||||
return {
|
||||
input: stringArrayValue(value.input),
|
||||
output: stringArrayValue(value.output),
|
||||
}
|
||||
}
|
||||
|
||||
function readCatalogWeights(value: unknown) {
|
||||
if (!Array.isArray(value)) return []
|
||||
return value.flatMap((item) => {
|
||||
if (!isRecord(item)) return []
|
||||
const label = stringValue(item.label)
|
||||
const url = stringValue(item.url)
|
||||
return label && url ? [{ label, url }] : []
|
||||
})
|
||||
}
|
||||
|
||||
function readCatalogBenchmarks(value: unknown) {
|
||||
if (!Array.isArray(value)) return []
|
||||
return value.flatMap((item) => {
|
||||
if (!isRecord(item)) return []
|
||||
const name = stringValue(item.name)
|
||||
const score = numberValue(item.score)
|
||||
return name && score !== undefined
|
||||
? [
|
||||
{
|
||||
name,
|
||||
score,
|
||||
metric: stringValue(item.metric),
|
||||
harness: stringValue(item.harness),
|
||||
variant: stringValue(item.variant),
|
||||
dataset: stringValue(item.dataset),
|
||||
version: stringValue(item.version),
|
||||
source: stringValue(item.source),
|
||||
},
|
||||
]
|
||||
: []
|
||||
})
|
||||
}
|
||||
|
||||
function displayDateTime(value: string | undefined) {
|
||||
return value ? new Date(value).getTime() || 0 : 0
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
function stringValue(value: unknown) {
|
||||
return typeof value === "string" && value.trim() ? value : undefined
|
||||
}
|
||||
|
||||
function numberValue(value: unknown) {
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : undefined
|
||||
}
|
||||
|
||||
function booleanValue(value: unknown) {
|
||||
return value === true
|
||||
}
|
||||
|
||||
function stringArrayValue(value: unknown) {
|
||||
return Array.isArray(value)
|
||||
? value.filter((item): item is string => typeof item === "string" && item.trim() !== "")
|
||||
: []
|
||||
}
|
||||
476
packages/stats/app/src/routes/stats-shell.tsx
Normal file
476
packages/stats/app/src/routes/stats-shell.tsx
Normal file
@@ -0,0 +1,476 @@
|
||||
import opencodeWordmarkDark from "../asset/logo-ornate-dark.svg"
|
||||
import { query } from "@solidjs/router"
|
||||
import { createEffect, createMemo, createSignal, For, onCleanup, onMount, Show } from "solid-js"
|
||||
|
||||
export type HeaderLink = { href: string; label: string }
|
||||
|
||||
export const headerLinks = [
|
||||
{ href: "#top-models", label: "Top Models" },
|
||||
{ href: "#leaderboard", label: "Leaderboard" },
|
||||
{ href: "#session-cost", label: "Session Cost" },
|
||||
{ href: "#token-cost", label: "Token Cost" },
|
||||
{ href: "#cache-ratio", label: "Cache Ratio" },
|
||||
{ href: "#market-share", label: "Market Share" },
|
||||
{ href: "#geo-breakdown", label: "Geo Breakdown" },
|
||||
] as const
|
||||
export const githubLink = {
|
||||
href: "https://github.com/anomalyco/opencode",
|
||||
apiHref: "https://api.github.com/repos/anomalyco/opencode",
|
||||
label: "GitHub",
|
||||
fallbackStars: "150K",
|
||||
ariaLabel: "Star OpenCode on GitHub",
|
||||
}
|
||||
export const themePreferences = ["dark", "light", "system"] as const
|
||||
export const themeStorageKey = "opencode:stats-theme"
|
||||
export type ThemePreference = (typeof themePreferences)[number]
|
||||
|
||||
const compactNumberFormatter = new Intl.NumberFormat("en", {
|
||||
notation: "compact",
|
||||
maximumFractionDigits: 1,
|
||||
})
|
||||
const themePreferenceLabels = {
|
||||
dark: "Dark",
|
||||
light: "Light",
|
||||
system: "System",
|
||||
} as const
|
||||
|
||||
export const getGitHubStars = query(async () => {
|
||||
"use server"
|
||||
return fetch(githubLink.apiHref, {
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
},
|
||||
})
|
||||
.then((response) => (response.ok ? response.json() : undefined))
|
||||
.then((body: unknown) =>
|
||||
body && typeof body === "object" && "stargazers_count" in body && typeof body.stargazers_count === "number"
|
||||
? compactNumberFormatter.format(body.stargazers_count)
|
||||
: githubLink.fallbackStars,
|
||||
)
|
||||
.catch(() => githubLink.fallbackStars)
|
||||
}, "getGitHubStars")
|
||||
|
||||
export function isThemePreference(value: string | null): value is ThemePreference {
|
||||
return value === "dark" || value === "light" || value === "system"
|
||||
}
|
||||
|
||||
export function applyThemePreference(preference: ThemePreference) {
|
||||
if (typeof document === "undefined") return
|
||||
document.documentElement.dataset.statsTheme = preference
|
||||
if (preference === "system") {
|
||||
document.documentElement.style.removeProperty("color-scheme")
|
||||
return
|
||||
}
|
||||
document.documentElement.style.setProperty("color-scheme", preference)
|
||||
}
|
||||
|
||||
export function Header(props: { githubStars: string; links?: readonly HeaderLink[]; brandHref?: string }) {
|
||||
const [menuOpen, setMenuOpen] = createSignal(false)
|
||||
const [menuViewport, setMenuViewport] = createSignal(false)
|
||||
const links = createMemo(() => props.links ?? headerLinks)
|
||||
|
||||
createEffect(() => {
|
||||
if (typeof window === "undefined") return
|
||||
const media = window.matchMedia("(max-width: 74.999rem)")
|
||||
const update = () => setMenuViewport(media.matches)
|
||||
update()
|
||||
media.addEventListener("change", update)
|
||||
onCleanup(() => media.removeEventListener("change", update))
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (!menuOpen()) return
|
||||
if (!menuViewport()) return
|
||||
if (typeof document === "undefined") return
|
||||
const page = document.querySelector<HTMLElement>('[data-page="stats"]')
|
||||
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth
|
||||
const htmlOverflow = document.documentElement.style.overflow
|
||||
const pagePaddingRight = page?.style.paddingRight
|
||||
const bodyOverflow = document.body.style.overflow
|
||||
document.documentElement.style.overflow = "hidden"
|
||||
if (scrollbarWidth > 0 && page) page.style.paddingRight = `${scrollbarWidth}px`
|
||||
document.body.style.overflow = "hidden"
|
||||
onCleanup(() => {
|
||||
document.documentElement.style.overflow = htmlOverflow
|
||||
if (page && pagePaddingRight !== undefined) page.style.paddingRight = pagePaddingRight
|
||||
document.body.style.overflow = bodyOverflow
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<header data-component="top" data-menu-open={menuOpen() ? "true" : undefined}>
|
||||
<div data-slot="header-bar">
|
||||
<a data-slot="brand" href={props.brandHref ?? import.meta.env.BASE_URL} aria-label="Data home">
|
||||
<DataWordmark />
|
||||
</a>
|
||||
<nav data-component="section-nav" aria-label="Data sections">
|
||||
<ul>
|
||||
<For each={links()}>
|
||||
{(link) => (
|
||||
<li>
|
||||
<a href={link.href}>{link.label}</a>
|
||||
</li>
|
||||
)}
|
||||
</For>
|
||||
</ul>
|
||||
</nav>
|
||||
<div data-slot="header-actions">
|
||||
<a
|
||||
data-slot="header-button"
|
||||
data-variant="neutral"
|
||||
href={githubLink.href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
aria-label={`${githubLink.ariaLabel} (${props.githubStars} stars)`}
|
||||
>
|
||||
<strong>{githubLink.label}</strong>
|
||||
<span>[{props.githubStars}]</span>
|
||||
</a>
|
||||
<a data-slot="header-button" data-variant="contrast" href="https://opencode.ai/">
|
||||
<strong>Try OpenCode</strong>
|
||||
</a>
|
||||
<button
|
||||
data-slot="menu-button"
|
||||
type="button"
|
||||
aria-controls="stats-mobile-nav"
|
||||
aria-expanded={menuOpen() ? "true" : "false"}
|
||||
aria-label={menuOpen() ? "Close navigation" : "Open navigation"}
|
||||
onClick={() => setMenuOpen((value) => !value)}
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<Show when={menuOpen()} fallback={<path d="M2 4.72H14M2 8.5H14M2 12.28H14" stroke="currentColor" />}>
|
||||
<path d="M4.44 4.44L11.56 11.56M11.56 4.44L4.44 11.56" stroke="currentColor" />
|
||||
</Show>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<nav id="stats-mobile-nav" data-slot="mobile-menu" aria-label="Data sections" hidden={!menuOpen()}>
|
||||
<a
|
||||
data-slot="mobile-menu-item"
|
||||
data-variant="github"
|
||||
href={githubLink.href}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
aria-label={`${githubLink.ariaLabel} (${props.githubStars} stars)`}
|
||||
>
|
||||
<strong>{githubLink.label}</strong>
|
||||
<span>[{props.githubStars}]</span>
|
||||
</a>
|
||||
<For each={links()}>
|
||||
{(link) => (
|
||||
<a data-slot="mobile-menu-item" href={link.href} onClick={() => setMenuOpen(false)}>
|
||||
{link.label}
|
||||
</a>
|
||||
)}
|
||||
</For>
|
||||
</nav>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
function DataWordmark() {
|
||||
return (
|
||||
<svg data-slot="stats-wordmark" width="66" height="20" viewBox="0 0 66 20" fill="none" aria-hidden="true">
|
||||
<path opacity="0.2" d="M12 16H4V8H12V16Z" fill="currentColor" />
|
||||
<path d="M12 4H4V16H12V4ZM16 20H0V0H16V20Z" fill="currentColor" />
|
||||
<path
|
||||
d="M63.3543 16L62.5119 12.8711H58.6437L57.8013 16H55.7383L59.2454 4H61.9618L65.4689 16H63.3543ZM61.0678 7.851L60.6896 5.94269H60.4489L60.0707 7.851L59.1595 11.1347H61.9962L61.0678 7.851Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path d="M52.5951 5.87392V16H50.4461V5.87392H47.4375V4H55.6209V5.87392H52.5951Z" fill="currentColor" />
|
||||
<path
|
||||
d="M45.2059 16L44.3635 12.8711H40.4953L39.6529 16H37.5898L41.097 4H43.8133L47.3205 16H45.2059ZM42.9194 7.851L42.5411 5.94269H42.3004L41.9222 7.851L41.011 11.1347H43.8477L42.9194 7.851Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M28 4H32.0917C32.8138 4 33.4556 4.11461 34.0172 4.34384C34.5903 4.5616 35.0716 4.9169 35.4613 5.40974C35.8625 5.89112 36.1662 6.51003 36.3725 7.26648C36.5788 8.02292 36.6819 8.9341 36.6819 10C36.6819 11.0659 36.5788 11.9771 36.3725 12.7335C36.1662 13.49 35.8625 14.1146 35.4613 14.6075C35.0716 15.0888 34.5903 15.4441 34.0172 15.6734C33.4556 15.8911 32.8138 16 32.0917 16H28V4ZM32.0917 14.1261C32.8252 14.1261 33.3926 13.9026 33.7937 13.4556C34.1948 12.9971 34.3954 12.3152 34.3954 11.4097V8.59026C34.3954 7.68481 34.1948 7.0086 33.7937 6.5616C33.3926 6.10315 32.8252 5.87392 32.0917 5.87392H30.149V14.1261H32.0917Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function OpenCodeMark() {
|
||||
return (
|
||||
<svg data-slot="opencode-mark" width="40" height="40" viewBox="0 0 40 40" fill="none" aria-hidden="true">
|
||||
<path d="M40 40H0V0H40V40Z" fill="var(--stats-logo-bg)" />
|
||||
<path d="M26 29H14V17H26V29Z" fill="var(--stats-logo-fill)" />
|
||||
<path d="M26 11H14V29H26V11ZM32 35H8V5H32V35Z" fill="var(--stats-logo-stroke)" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function Footer(props: {
|
||||
themePreference: ThemePreference
|
||||
onThemePreferenceChange: (preference: ThemePreference) => void
|
||||
links?: readonly HeaderLink[]
|
||||
}) {
|
||||
const [subscribeOpen, setSubscribeOpen] = createSignal(false)
|
||||
const modelStats = props.links ?? [
|
||||
{ href: "#top-models", label: "Top Models" },
|
||||
{ href: "#leaderboard", label: "Leaderboard" },
|
||||
{ href: "#session-cost", label: "Session Cost" },
|
||||
{ href: "#token-cost", label: "Token Cost" },
|
||||
{ href: "#cache-ratio", label: "Cache Ratio" },
|
||||
{ href: "#market-share", label: "Market Share" },
|
||||
{ href: "#geo-breakdown", label: "Geo Breakdown" },
|
||||
]
|
||||
const legal = [
|
||||
{ href: "https://opencode.ai/legal/terms-of-service", label: "Terms of service" },
|
||||
{ href: "https://opencode.ai/legal/privacy-policy", label: "Privacy policy" },
|
||||
]
|
||||
const connect = [
|
||||
{ href: "mailto:hello@opencode.ai", label: "Contact us" },
|
||||
{ href: "https://opencode.ai/discord", label: "Community" },
|
||||
{ href: "https://x.com/opencode", label: "X" },
|
||||
githubLink,
|
||||
{ href: "https://www.youtube.com/@anomaly-co", label: "YouTube" },
|
||||
]
|
||||
|
||||
return (
|
||||
<footer data-component="footer">
|
||||
<SectionBridge label="GEO BREAKDOWN" href="#geo-breakdown" />
|
||||
<div data-slot="footer-grid">
|
||||
<a data-slot="footer-mark" href="https://opencode.ai" aria-label="OpenCode home">
|
||||
<OpenCodeMark />
|
||||
</a>
|
||||
<FooterColumn title="Model Data" links={modelStats} />
|
||||
<FooterColumn title="Legal" links={legal} />
|
||||
<FooterColumn title="Connect" links={connect} />
|
||||
<div data-slot="footer-column">
|
||||
<h2>Newsletter</h2>
|
||||
<p>Be the first to know about new releases.</p>
|
||||
<button data-slot="subscribe-button" type="button" onClick={() => setSubscribeOpen(true)}>
|
||||
Subscribe
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div data-slot="footer-pattern" aria-hidden="true" />
|
||||
<div data-slot="footer-bottom">
|
||||
<div>
|
||||
<span>© 2026 Anomaly Innovations Inc.</span>
|
||||
<span data-slot="status">All systems Operational</span>
|
||||
</div>
|
||||
<div data-slot="theme-toggle" role="group" aria-label="Theme">
|
||||
<For each={themePreferences}>
|
||||
{(preference) => (
|
||||
<button
|
||||
data-slot="theme-option"
|
||||
type="button"
|
||||
aria-label={themePreferenceLabels[preference]}
|
||||
aria-pressed={props.themePreference === preference ? "true" : "false"}
|
||||
title={themePreferenceLabels[preference]}
|
||||
onClick={() => props.onThemePreferenceChange(preference)}
|
||||
>
|
||||
<ThemePreferenceIcon preference={preference} />
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={subscribeOpen()}>
|
||||
<SubscribeModal onClose={() => setSubscribeOpen(false)} />
|
||||
</Show>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
function SectionBridge(props: { label: string; href: string }) {
|
||||
return (
|
||||
<a data-component="section-bridge" href={props.href}>
|
||||
<span>LEAN MORE</span>
|
||||
<i />
|
||||
<strong>{props.label}</strong>
|
||||
<b>▸</b>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
function ThemePreferenceIcon(props: { preference: ThemePreference }) {
|
||||
return (
|
||||
<svg data-slot="theme-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<Show
|
||||
when={props.preference === "dark"}
|
||||
fallback={
|
||||
<Show
|
||||
when={props.preference === "light"}
|
||||
fallback={
|
||||
<>
|
||||
<rect x="1.5552" y="2.4448" width="12.8896" height="8.8888" fill="currentColor" opacity="0.3" />
|
||||
<svg
|
||||
x="1.0552"
|
||||
y="1.9446"
|
||||
width="13.8889"
|
||||
height="12.5325"
|
||||
viewBox="0 0 13.8889 12.5325"
|
||||
preserveAspectRatio="none"
|
||||
overflow="visible"
|
||||
>
|
||||
<path
|
||||
d="M4.05559 12.0555C4.72936 11.8431 5.72492 11.6111 6.94448 11.6111M6.94448 11.6111C7.65114 11.6111 8.66981 11.6893 9.83336 12.0555M6.94448 11.6111L6.94448 9.38888M13.3889 0.5H0.500102C0.500102 0.5 0.500017 1.29594 0.500017 2.27778V7.61112C0.500017 8.59298 0.500007 9.38889 0.500007 9.38889H13.3889C13.3889 9.38889 13.3889 8.59298 13.3889 7.61112V2.27778C13.3889 1.29594 13.3889 0.5 13.3889 0.5Z"
|
||||
stroke="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<svg
|
||||
x="0.6102"
|
||||
y="0.6102"
|
||||
width="14.7778"
|
||||
height="14.7778"
|
||||
viewBox="0 0 14.7778 14.7778"
|
||||
preserveAspectRatio="none"
|
||||
overflow="visible"
|
||||
>
|
||||
<path
|
||||
d="M7.38889 0.5V1.38889M12.26 2.51782L11.6315 3.14627M14.2778 7.38892H13.3889M12.26 12.26L11.6315 11.6316M7.38889 14.2778V13.3889M2.51778 12.26L3.14622 11.6316M0.5 7.38892H1.38889M2.51778 2.51782L3.14622 3.14627M7.38888 11.1666C9.47528 11.1666 11.1667 9.47526 11.1667 7.38886C11.1667 5.30245 9.47528 3.61108 7.38888 3.61108C5.30247 3.61108 3.6111 5.30245 3.6111 7.38886C3.6111 9.47526 5.30247 11.1666 7.38888 11.1666Z"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="square"
|
||||
/>
|
||||
</svg>
|
||||
</Show>
|
||||
}
|
||||
>
|
||||
<svg
|
||||
x="2.0549"
|
||||
y="1.742"
|
||||
width="12.3867"
|
||||
height="12.3971"
|
||||
viewBox="0 0 12.3867 12.3971"
|
||||
preserveAspectRatio="none"
|
||||
overflow="visible"
|
||||
>
|
||||
<path
|
||||
d="M9.05556 8.39711C6.37067 8.39711 4.19444 6.22089 4.19444 3.536C4.19444 2.48445 4.53122 1.51456 5.09822 0.71889C2.48178 1.20733 0.5 3.49944 0.5 6.25822C0.5 9.37244 3.02467 11.8971 6.13889 11.8971C8.76156 11.8971 10.9596 10.1036 11.5903 7.67844C10.8514 8.13189 9.98578 8.39711 9.05556 8.39711Z"
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
</Show>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function SubscribeModal(props: { onClose: () => void }) {
|
||||
const [status, setStatus] = createSignal<"idle" | "pending" | "success" | "error">("idle")
|
||||
const [message, setMessage] = createSignal("")
|
||||
let input: HTMLInputElement | undefined
|
||||
|
||||
onMount(() => {
|
||||
if (typeof document === "undefined") return
|
||||
const activeElement = document.activeElement instanceof HTMLElement ? document.activeElement : undefined
|
||||
const htmlOverflow = document.documentElement.style.overflow
|
||||
const bodyOverflow = document.body.style.overflow
|
||||
document.documentElement.style.overflow = "hidden"
|
||||
document.body.style.overflow = "hidden"
|
||||
const focusTimeout = window.setTimeout(() => input?.focus(), 0)
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape") props.onClose()
|
||||
}
|
||||
document.addEventListener("keydown", onKeyDown)
|
||||
onCleanup(() => {
|
||||
window.clearTimeout(focusTimeout)
|
||||
document.documentElement.style.overflow = htmlOverflow
|
||||
document.body.style.overflow = bodyOverflow
|
||||
document.removeEventListener("keydown", onKeyDown)
|
||||
activeElement?.focus()
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<div data-component="subscribe-modal" role="dialog" aria-modal="true" aria-labelledby="subscribe-title">
|
||||
<div data-slot="modal-scrim" aria-hidden="true" onClick={props.onClose} />
|
||||
<div data-slot="modal-panel">
|
||||
<div data-slot="modal-brand">
|
||||
<img data-slot="modal-logo" src={opencodeWordmarkDark} alt="OpenCode" />
|
||||
<button data-slot="modal-close" type="button" aria-label="Close newsletter signup" onClick={props.onClose}>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path d="M4.44 4.44L11.56 11.56M11.56 4.44L4.44 11.56" stroke="currentColor" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div data-slot="modal-body">
|
||||
<div data-slot="modal-intro">
|
||||
<h2 id="subscribe-title">OpenCode Newsletter</h2>
|
||||
<p>
|
||||
Be the first to know
|
||||
<br />
|
||||
about new releases.
|
||||
</p>
|
||||
</div>
|
||||
<form
|
||||
data-slot="subscribe-form"
|
||||
method="post"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault()
|
||||
const form = event.currentTarget
|
||||
setStatus("pending")
|
||||
setMessage("")
|
||||
fetch(`${import.meta.env.BASE_URL}api/newsletter`, {
|
||||
method: "POST",
|
||||
body: new FormData(form),
|
||||
}).then(
|
||||
async (response) => {
|
||||
if (response.ok) {
|
||||
form.reset()
|
||||
setStatus("success")
|
||||
return
|
||||
}
|
||||
setMessage(await newsletterErrorMessage(response))
|
||||
setStatus("error")
|
||||
},
|
||||
() => {
|
||||
setMessage("Failed to subscribe")
|
||||
setStatus("error")
|
||||
},
|
||||
)
|
||||
}}
|
||||
>
|
||||
<input ref={input} type="email" name="email" placeholder="Email address" required />
|
||||
<button type="submit" disabled={status() === "pending"}>
|
||||
<span>{status() === "pending" ? "Subscribing..." : "Subscribe"}</span>
|
||||
</button>
|
||||
</form>
|
||||
<div data-slot="subscribe-feedback" aria-live="polite">
|
||||
<Show when={status() === "success"}>
|
||||
<p data-state="success">You're subscribed.</p>
|
||||
</Show>
|
||||
<Show when={status() === "error"}>
|
||||
<p data-state="error">{message()}</p>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function newsletterErrorMessage(response: Response) {
|
||||
return response.json().then(
|
||||
(body: unknown) =>
|
||||
body && typeof body === "object" && "error" in body && typeof body.error === "string"
|
||||
? body.error
|
||||
: "Failed to subscribe",
|
||||
() => "Failed to subscribe",
|
||||
)
|
||||
}
|
||||
|
||||
function FooterColumn(props: { title: string; links: readonly { href: string; label: string }[] }) {
|
||||
return (
|
||||
<div data-slot="footer-column">
|
||||
<h2>{props.title}</h2>
|
||||
<nav aria-label={props.title}>
|
||||
<For each={props.links}>
|
||||
{(link) => (
|
||||
<a href={link.href} target={link.href.startsWith("http") ? "_blank" : undefined} rel="noreferrer">
|
||||
{link.label}
|
||||
</a>
|
||||
)}
|
||||
</For>
|
||||
</nav>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
1
packages/stats/app/src/routes/stats/api/health.ts
Normal file
1
packages/stats/app/src/routes/stats/api/health.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { GET } from "../../api/health"
|
||||
1
packages/stats/app/src/routes/stats/api/newsletter.ts
Normal file
1
packages/stats/app/src/routes/stats/api/newsletter.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { POST } from "../../api/newsletter"
|
||||
Reference in New Issue
Block a user