diff --git a/packages/stats/app/src/i18n.ts b/packages/stats/app/src/i18n.ts index eaa6f1674c..1cffda4430 100644 --- a/packages/stats/app/src/i18n.ts +++ b/packages/stats/app/src/i18n.ts @@ -198,7 +198,7 @@ const en = { "model.knowledge": "Knowledge", "model.release": "Release", "model.inputs": "Inputs", - "model.overviewDescription": "Recent OpenCode Go tokens, unique users, and market position.", + "model.overviewDescription": "Recent tokens, unique users, and market position.", "model.noSummaryTitle": "No usage summary", "model.noSummaryDescription": "This model has no OpenCode Go usage rows yet.", "model.tokens": "Tokens", diff --git a/packages/stats/app/src/routes/[lab]/[model].tsx b/packages/stats/app/src/routes/[lab]/[model].tsx index 300856590d..0c882ab687 100644 --- a/packages/stats/app/src/routes/[lab]/[model].tsx +++ b/packages/stats/app/src/routes/[lab]/[model].tsx @@ -48,6 +48,7 @@ import { const statsUnfurlPath = "banner.png" const geoMapWidth = 960 const geoMapHeight = 430 +const shortMonths = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"] as const type IsoCountryCode = readonly [string, string, string] type WorldCountryProperties = GeoJsonProperties & { name?: string } @@ -119,8 +120,7 @@ export default function StatsModel() { const statsUnfurlUrl = new URL(statsUnfurlPath, localizedUrl("en", "/data/")).toString() const modelHeaderLinks = createMemo(() => [ { href: "#overview", label: i18n.t("nav.overview") }, - { href: "#usage", label: i18n.t("nav.usage") }, - { href: "#users", label: i18n.t("nav.users") }, + { href: "#momentum", label: i18n.t("model.momentum") }, { href: "#efficiency", label: i18n.t("nav.efficiency") }, { href: "#geo-breakdown", label: i18n.t("nav.geoBreakdown") }, { href: "#peers", label: i18n.t("nav.peers") }, @@ -181,9 +181,8 @@ export default function StatsModel() { catalogData={catalog() ?? null} labName={labName()} /> - - - + + @@ -423,15 +422,59 @@ function ChevronDownIcon() { ) } -function ModelOverview(props: { data: StatsModelData | null }) { +function ModelOverview(props: { catalog: ModelCatalogEntry | null }) { const i18n = useI18n() + const language = useLanguage() + const specs = createMemo(() => [ + { + label: i18n.t("model.context"), + value: formatCatalogLimit(props.catalog?.limit?.context, i18n.t("home.unknown")), + }, + { + label: i18n.t("model.output"), + value: formatCatalogLimit(props.catalog?.limit?.output, i18n.t("home.unknown")), + }, + { + label: i18n.t("model.knowledge"), + value: formatCatalogMonth(props.catalog?.knowledge, language.tag(language.locale()), i18n.t("home.unknown")), + }, + { + label: i18n.t("model.release"), + value: formatCatalogMonth(props.catalog?.releaseDate, language.tag(language.locale()), i18n.t("home.unknown")), + }, + { + label: i18n.t("model.inputs"), + value: formatCatalogModalities( + props.catalog?.modalities.input ?? [], + language.tag(language.locale()), + i18n.t("home.unknown"), + ), + }, + ]) return ( -
- +
+ + {(spec) => ( +
+ {spec.label} + {spec.value} +
+ )} +
+
+ ) +} + +function ModelMomentumSection(props: { data: StatsModelData | null }) { + const i18n = useI18n() + const language = useLanguage() + return ( +
+

+ {i18n.t("model.momentum")}. + {i18n.t("model.overviewDescription")} +

+
) } -function ModelUsageSection(props: { data: ModelUsagePoint[] }) { - const i18n = useI18n() +function MomentumChart(props: { data: StatsModelData; locale: string }) { + const chart = createMemo(() => momentumChart(props.data.usage, props.data.updatedAt)) + const changeState = createMemo(() => (props.data.tokenChange < 0 ? "negative" : "positive")) return ( -
- - item.tokens > 0)} - fallback={ - - } - > - - -
- ) -} - -function ModelUsersSection(props: { data: ModelUsagePoint[] }) { - const i18n = useI18n() - return ( -
- - item.users > 0)} - fallback={ - - } - > - - -
- ) -} - -function ModelColumnChart(props: { data: ModelUsagePoint[]; metric: "tokens" | "users"; ariaLabel: string }) { - const i18n = useI18n() - const [activeIndex, setActiveIndex] = createSignal() - const max = createMemo(() => Math.max(0, ...props.data.map((item) => modelUsageMetricValue(item, props.metric))) || 1) - const activePoint = createMemo(() => { - const index = activeIndex() - if (index === undefined) return undefined - return props.data[index] - }) - - return ( -
{ - if (event.pointerType === "touch") return - setActiveIndex(undefined) - }} - > -