Просмотр исходного кода

fix(stats): refine lab page tooltips

Adam 1 месяц назад
Родитель
Сommit
beb586a383

+ 7 - 1
packages/stats/app/src/routes/[lab]/index.tsx

@@ -469,6 +469,12 @@ function LabUsageSection(props: { lab: ModelCatalogLab; data: StatsLabData | nul
                       </span>
                       </span>
                       <b>{formatTokens(active.point.tokens)}</b>
                       <b>{formatTokens(active.point.tokens)}</b>
                     </p>
                     </p>
+                    <p>
+                      <span data-slot="tooltip-label">
+                        <i data-kind="users" /> {i18n.t("model.uniqueUsers")}
+                      </span>
+                      <b>{formatUsers(active.point.users)}</b>
+                    </p>
                   </div>
                   </div>
                 )
                 )
               }}
               }}
@@ -759,7 +765,7 @@ function relatedLabStats(labs: ModelCatalogLab[], market: MarketDay[]) {
 }
 }
 
 
 function labRelatedDescription(lab: ModelCatalogLab) {
 function labRelatedDescription(lab: ModelCatalogLab) {
-  return lab.description ?? `OpenCode Go usage across ${lab.name} models, recent token volume, and model limits.`
+  return lab.description ?? ""
 }
 }
 
 
 function relatedTone(share: number) {
 function relatedTone(share: number) {

+ 9 - 2
packages/stats/app/src/routes/index.css

@@ -4218,7 +4218,7 @@
 
 
 [data-page="stats"] [data-component="model-usage-chart"][data-variant="lab-usage"] [data-component="chart-tooltip"] {
 [data-page="stats"] [data-component="model-usage-chart"][data-variant="lab-usage"] [data-component="chart-tooltip"] {
   z-index: 6;
   z-index: 6;
-  top: clamp(12px, calc(var(--lab-usage-user-y) * 1% - 38px), calc(100% - 92px));
+  top: clamp(12px, calc(var(--lab-usage-user-y) * 1% - 46px), calc(100% - 108px));
   width: 192px;
   width: 192px;
   min-width: 192px;
   min-width: 192px;
   background: var(--stats-bg);
   background: var(--stats-bg);
@@ -4240,7 +4240,7 @@
 
 
 [data-page="stats"] [data-component="model-usage-chart"][data-variant="lab-usage"] [data-component="chart-tooltip"] p {
 [data-page="stats"] [data-component="model-usage-chart"][data-variant="lab-usage"] [data-component="chart-tooltip"] p {
   height: 16px;
   height: 16px;
-  margin: 8px 0;
+  margin: 4px 0;
 }
 }
 
 
 [data-page="stats"]
 [data-page="stats"]
@@ -4250,6 +4250,13 @@
   background: var(--lab-usage-active);
   background: var(--lab-usage-active);
 }
 }
 
 
+[data-page="stats"]
+  [data-component="model-usage-chart"][data-variant="lab-usage"]
+  [data-component="chart-tooltip"]
+  i[data-kind="users"] {
+  background: var(--lab-usage-line);
+}
+
 [data-page="stats"] [data-component="model-peer-list"] {
 [data-page="stats"] [data-component="model-peer-list"] {
   display: grid;
   display: grid;
   gap: 8px;
   gap: 8px;

+ 23 - 5
packages/stats/app/src/routes/model-catalog.ts

@@ -2,6 +2,7 @@ import { query } from "@solidjs/router"
 
 
 export const modelCatalogSourceUrl = "https://models.dev/catalog.json"
 export const modelCatalogSourceUrl = "https://models.dev/catalog.json"
 export const modelCatalogPricingUrl = "https://models.dev/api.json"
 export const modelCatalogPricingUrl = "https://models.dev/api.json"
+export const modelCatalogLabSourceUrl = "https://models.dev/labs"
 
 
 export type ModelCatalogCost = {
 export type ModelCatalogCost = {
   input: number
   input: number
@@ -57,11 +58,12 @@ export type ModelCatalog = {
 
 
 export const getModelCatalog = query(async () => {
 export const getModelCatalog = query(async () => {
   "use server"
   "use server"
-  const [models, pricing] = await Promise.all([
+  const [models, pricing, labs] = await Promise.all([
     fetchCatalogPayload(modelCatalogSourceUrl),
     fetchCatalogPayload(modelCatalogSourceUrl),
     fetchCatalogPayload(modelCatalogPricingUrl),
     fetchCatalogPayload(modelCatalogPricingUrl),
+    fetchLabCatalogPayload(modelCatalogLabSourceUrl),
   ])
   ])
-  return buildModelCatalog(models, pricing)
+  return buildModelCatalog(models, pricing, labs)
 }, "getModelCatalog")
 }, "getModelCatalog")
 
 
 export function findModelCatalogEntry(catalog: ModelCatalog, model: string, lab?: string) {
 export function findModelCatalogEntry(catalog: ModelCatalog, model: string, lab?: string) {
@@ -113,9 +115,9 @@ export function catalogSlug(value: string) {
     .replace(/-{2,}/g, "-")
     .replace(/-{2,}/g, "-")
 }
 }
 
 
-function buildModelCatalog(payload: unknown, pricingPayload?: unknown): ModelCatalog {
+function buildModelCatalog(payload: unknown, pricingPayload?: unknown, labPayload?: unknown): ModelCatalog {
   const costs = readCatalogCosts(pricingPayload)
   const costs = readCatalogCosts(pricingPayload)
-  const labDescriptions = readCatalogLabDescriptions(payload, pricingPayload)
+  const labDescriptions = readCatalogLabDescriptions(payload, pricingPayload, labPayload)
   const models = readCatalogModels(payload)
   const models = readCatalogModels(payload)
     .flatMap(readModelCatalogEntry)
     .flatMap(readModelCatalogEntry)
     .map((model) => ({
     .map((model) => ({
@@ -191,7 +193,8 @@ function readCatalogLabDescriptions(...payloads: unknown[]) {
     if (!description) return
     if (!description) return
     const id = stringValue(value.id) ?? fallbackId
     const id = stringValue(value.id) ?? fallbackId
     const name = stringValue(value.name)
     const name = stringValue(value.name)
-    const keys = [id, name]
+    const title = stringValue(value.title)
+    const keys = [id, name, title]
     keys.forEach((key) => {
     keys.forEach((key) => {
       if (!key) return
       if (!key) return
       descriptions.set(catalogLabSlug(key), description)
       descriptions.set(catalogLabSlug(key), description)
@@ -226,6 +229,21 @@ async function fetchCatalogPayload(url: string) {
     .catch(() => undefined)
     .catch(() => undefined)
 }
 }
 
 
+async function fetchLabCatalogPayload(url: string) {
+  return fetch(url)
+    .then((response) => (response.ok ? response.text() : Promise.resolve("")))
+    .then(readLabSearchIndex)
+    .catch(() => undefined)
+}
+
+function readLabSearchIndex(html: string) {
+  const match = /<script[^>]*id=["']search-index["'][^>]*>([\s\S]*?)<\/script>/.exec(html)
+  if (!match) return undefined
+  const parsed = JSON.parse(match[1]) as unknown
+  if (!Array.isArray(parsed)) return undefined
+  return parsed.filter((item) => isRecord(item) && item.type === "lab")
+}
+
 function readCatalogCosts(payload: unknown) {
 function readCatalogCosts(payload: unknown) {
   const costs = new Map<string, ModelCatalogCost>()
   const costs = new Map<string, ModelCatalogCost>()
   const add = (model: unknown, provider?: string) => {
   const add = (model: unknown, provider?: string) => {