Procházet zdrojové kódy

feat(data): redesign model efficiency

Adam před 2 měsíci
rodič
revize
ea9fffce2e

+ 6 - 1
packages/stats/app/src/i18n.ts

@@ -218,17 +218,22 @@ const en = {
   "model.noUsersTitle": "No user data",
   "model.noUsersDescription": "No user-bearing rows landed in the current window.",
   "model.dailyUserChart": "Daily unique user chart",
-  "model.efficiencyDescription": "Cost, cache behavior, and average OpenCode Go session shape.",
+  "model.efficiencyDescription": "Cost, cache behavior, and average session shape.",
   "model.noEfficiencyTitle": "No efficiency data",
   "model.noEfficiencyDescription": "Efficiency data appears after usage lands.",
+  "model.totalSpendLabel": "Total spend",
   "model.cost": "Cost",
   "model.totalSpend": "total spend",
   "model.costPerMillion": "Cost / 1M",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
   "model.inputOutput": "input / output",
   "model.observedTokens": "observed all tokens",
   "model.costSession": "Cost / Session",
+  "model.averageCostSession": "Average cost / Session",
   "model.average": "average",
   "model.tokensSession": "Tokens / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cacheRatio": "Cache Ratio",
   "model.inputTokens": "input tokens",
   "model.geoDescription": "OpenCode Go model tokens used by country.",

+ 5 - 0
packages/stats/app/src/i18n/ar.ts

@@ -202,6 +202,11 @@ export const dict = {
   "model.efficiencyDescription": "التكلفة، وسلوك التخزين المؤقت، ومتوسط شكل جلسة OpenCode Go.",
   "model.noEfficiencyTitle": "لا توجد بيانات كفاءة",
   "model.noEfficiencyDescription": "تظهر بيانات الكفاءة بعد وصول الاستخدام.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "التكلفة",
   "model.totalSpend": "إجمالي الإنفاق",
   "model.costPerMillion": "التكلفة / 1M",

+ 5 - 0
packages/stats/app/src/i18n/br.ts

@@ -204,6 +204,11 @@ export const dict = {
   "model.efficiencyDescription": "Custo, comportamento de cache e formato médio da sessão do OpenCode Go.",
   "model.noEfficiencyTitle": "Sem dados de eficiência",
   "model.noEfficiencyDescription": "Os dados de eficiência aparecem depois que o uso chega.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Custo",
   "model.totalSpend": "gasto total",
   "model.costPerMillion": "Custo / 1M",

+ 5 - 0
packages/stats/app/src/i18n/da.ts

@@ -203,6 +203,11 @@ export const dict = {
   "model.efficiencyDescription": "Omkostning, cacheadfærd og gennemsnitlig OpenCode Go-sessionsform.",
   "model.noEfficiencyTitle": "Ingen effektivitetsdata",
   "model.noEfficiencyDescription": "Effektivitetsdata vises, når brug lander.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Omkostning",
   "model.totalSpend": "samlet forbrug",
   "model.costPerMillion": "Omkostning / 1 mio.",

+ 5 - 0
packages/stats/app/src/i18n/de.ts

@@ -204,6 +204,11 @@ export const dict = {
   "model.efficiencyDescription": "Kosten, Cache-Verhalten und durchschnittliche OpenCode Go-Sitzungsform.",
   "model.noEfficiencyTitle": "Keine Effizienzdaten",
   "model.noEfficiencyDescription": "Effizienzdaten erscheinen, nachdem Nutzung eingegangen ist.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Kosten",
   "model.totalSpend": "Gesamtausgaben",
   "model.costPerMillion": "Kosten / 1 Mio.",

+ 5 - 0
packages/stats/app/src/i18n/es.ts

@@ -203,6 +203,11 @@ export const dict = {
   "model.efficiencyDescription": "Coste, comportamiento de caché y forma media de sesión de OpenCode Go.",
   "model.noEfficiencyTitle": "Sin datos de eficiencia",
   "model.noEfficiencyDescription": "Los datos de eficiencia aparecen después de que llegue uso.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Coste",
   "model.totalSpend": "gasto total",
   "model.costPerMillion": "Coste / 1 M",

+ 5 - 0
packages/stats/app/src/i18n/fr.ts

@@ -205,6 +205,11 @@ export const dict = {
   "model.efficiencyDescription": "Coût, comportement du cache et forme moyenne des sessions OpenCode Go.",
   "model.noEfficiencyTitle": "Aucune donnée d'efficacité",
   "model.noEfficiencyDescription": "Les données d'efficacité apparaissent après l'arrivée de l'utilisation.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Coût",
   "model.totalSpend": "dépense totale",
   "model.costPerMillion": "Coût / 1 M",

+ 5 - 0
packages/stats/app/src/i18n/it.ts

@@ -204,6 +204,11 @@ export const dict = {
   "model.efficiencyDescription": "Costi, comportamento della cache e forma media della sessione OpenCode Go.",
   "model.noEfficiencyTitle": "Nessun dato di efficienza",
   "model.noEfficiencyDescription": "I dati di efficienza appaiono dopo l'arrivo dell'utilizzo.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Costo",
   "model.totalSpend": "spesa totale",
   "model.costPerMillion": "Costo / 1 M",

+ 5 - 0
packages/stats/app/src/i18n/ja.ts

@@ -205,6 +205,11 @@ export const dict = {
   "model.efficiencyDescription": "コスト、キャッシュ動作、平均的なOpenCode Goセッションの形。",
   "model.noEfficiencyTitle": "効率データがありません",
   "model.noEfficiencyDescription": "使用量が届くと効率データが表示されます。",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "コスト",
   "model.totalSpend": "総支出",
   "model.costPerMillion": "100万あたりのコスト",

+ 5 - 0
packages/stats/app/src/i18n/ko.ts

@@ -205,6 +205,11 @@ export const dict = {
   "model.efficiencyDescription": "비용, 캐시 동작, 평균 OpenCode Go 세션 형태입니다.",
   "model.noEfficiencyTitle": "효율 데이터 없음",
   "model.noEfficiencyDescription": "사용량이 들어오면 효율 데이터가 표시됩니다.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "비용",
   "model.totalSpend": "총 지출",
   "model.costPerMillion": "100만당 비용",

+ 5 - 0
packages/stats/app/src/i18n/no.ts

@@ -203,6 +203,11 @@ export const dict = {
   "model.efficiencyDescription": "Kostnad, cacheatferd og gjennomsnittlig OpenCode Go-øktform.",
   "model.noEfficiencyTitle": "Ingen effektivitetsdata",
   "model.noEfficiencyDescription": "Effektivitetsdata vises etter at bruk lander.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Kostnad",
   "model.totalSpend": "totalt forbruk",
   "model.costPerMillion": "Kostnad / 1 mill.",

+ 5 - 0
packages/stats/app/src/i18n/pl.ts

@@ -202,6 +202,11 @@ export const dict = {
   "model.efficiencyDescription": "Koszt, zachowanie cache i średni kształt sesji OpenCode Go.",
   "model.noEfficiencyTitle": "Brak danych wydajności",
   "model.noEfficiencyDescription": "Dane wydajności pojawią się po nadejściu użycia.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Koszt",
   "model.totalSpend": "łączne wydatki",
   "model.costPerMillion": "Koszt / 1 mln",

+ 5 - 0
packages/stats/app/src/i18n/ru.ts

@@ -204,6 +204,11 @@ export const dict = {
   "model.efficiencyDescription": "Стоимость, поведение кэша и средняя форма сеанса OpenCode Go.",
   "model.noEfficiencyTitle": "Нет данных эффективности",
   "model.noEfficiencyDescription": "Данные эффективности появятся после использования.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Стоимость",
   "model.totalSpend": "общие расходы",
   "model.costPerMillion": "Стоимость / 1 млн",

+ 5 - 0
packages/stats/app/src/i18n/th.ts

@@ -204,6 +204,11 @@ export const dict = {
   "model.efficiencyDescription": "ต้นทุน พฤติกรรมแคช และรูปแบบเซสชัน OpenCode Go โดยเฉลี่ย",
   "model.noEfficiencyTitle": "ไม่มีข้อมูลประสิทธิภาพ",
   "model.noEfficiencyDescription": "ข้อมูลประสิทธิภาพจะแสดงหลังจากมีการใช้งานเข้ามา",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "ต้นทุน",
   "model.totalSpend": "ยอดใช้จ่ายรวม",
   "model.costPerMillion": "ต้นทุน / 1 ล้าน",

+ 5 - 0
packages/stats/app/src/i18n/tr.ts

@@ -204,6 +204,11 @@ export const dict = {
   "model.efficiencyDescription": "Maliyet, önbellek davranışı ve ortalama OpenCode Go oturum yapısı.",
   "model.noEfficiencyTitle": "Verimlilik verisi yok",
   "model.noEfficiencyDescription": "Verimlilik verileri kullanım geldikten sonra görünür.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Maliyet",
   "model.totalSpend": "toplam harcama",
   "model.costPerMillion": "Maliyet / 1M",

+ 5 - 0
packages/stats/app/src/i18n/uk.ts

@@ -204,6 +204,11 @@ export const dict = {
   "model.efficiencyDescription": "Вартість, поведінка кешу та середня форма сеансу OpenCode Go.",
   "model.noEfficiencyTitle": "Немає даних ефективності",
   "model.noEfficiencyDescription": "Дані ефективності з'являться після використання.",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "Вартість",
   "model.totalSpend": "загальні витрати",
   "model.costPerMillion": "Вартість / 1 млн",

+ 5 - 0
packages/stats/app/src/i18n/zh.ts

@@ -203,6 +203,11 @@ export const dict = {
   "model.efficiencyDescription": "成本、缓存行为和平均 OpenCode Go 会话形态。",
   "model.noEfficiencyTitle": "无效率数据",
   "model.noEfficiencyDescription": "使用量到达后会显示效率数据。",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "成本",
   "model.totalSpend": "总支出",
   "model.costPerMillion": "每 100 万成本",

+ 5 - 0
packages/stats/app/src/i18n/zht.ts

@@ -203,6 +203,11 @@ export const dict = {
   "model.efficiencyDescription": "成本、快取行為和平均 OpenCode Go 工作階段形態。",
   "model.noEfficiencyTitle": "無效率數據",
   "model.noEfficiencyDescription": "使用量到達後會顯示效率數據。",
+  "model.totalSpendLabel": "Total spend",
+  "model.costInput": "Cost input",
+  "model.costOutput": "Cost output",
+  "model.averageCostSession": "Average cost / Session",
+  "model.averageTokensSession": "Average tokens / session",
   "model.cost": "成本",
   "model.totalSpend": "總支出",
   "model.costPerMillion": "每 100 萬成本",

+ 25 - 34
packages/stats/app/src/routes/[lab]/[model].tsx

@@ -28,7 +28,6 @@ import {
   formatCatalogLabName,
   getModelCatalog,
   type ModelCatalog,
-  type ModelCatalogCost,
   type ModelCatalogEntry,
 } from "../model-catalog"
 import { SectionHeading } from "../section-heading"
@@ -881,35 +880,26 @@ function ModelEfficiencySection(props: { data: StatsModelData | null; catalog: M
         }
       >
         {(data) => (
-          <div data-component="model-metric-grid" data-variant="dense">
-            <MetricCard
-              label={i18n.t("model.cost")}
-              value={formatMoney(data().totals.cost)}
-              detail={i18n.t("model.totalSpend")}
-            />
-            <MetricCard
-              label={i18n.t("model.costPerMillion")}
-              value={
-                props.catalog?.cost ? formatCatalogPrice(props.catalog.cost) : formatMoney(data().totals.costPerMillion)
-              }
-              detail={props.catalog?.cost ? i18n.t("model.inputOutput") : i18n.t("model.observedTokens")}
-            />
-            <MetricCard
-              label={i18n.t("model.costSession")}
-              value={formatSessionCost(data().totals.costPerSession)}
-              detail={i18n.t("model.average")}
-            />
-            <MetricCard
-              label={i18n.t("model.tokensSession")}
-              value={formatTokens(data().totals.tokensPerSession)}
-              detail={i18n.t("model.average")}
-            />
-            <MetricCard
-              label={i18n.t("model.cacheRatio")}
-              value={formatPercent(data().totals.cacheRatio)}
-              detail={i18n.t("model.inputTokens")}
-            />
-          </div>
+          <>
+            <div data-slot="model-efficiency-pattern" aria-hidden="true" />
+            <div data-component="model-efficiency-grid">
+              <MetricCard label={i18n.t("model.totalSpendLabel")} value={formatMoney(data().totals.cost)} />
+              <MetricCard label={i18n.t("model.costInput")} value={formatCatalogUnitPrice(props.catalog?.cost?.input)} />
+              <MetricCard label={i18n.t("model.costOutput")} value={formatCatalogUnitPrice(props.catalog?.cost?.output)} />
+              <MetricCard
+                label={i18n.t("model.averageCostSession")}
+                value={formatSessionCost(data().totals.costPerSession)}
+              />
+              <MetricCard
+                label={i18n.t("model.averageTokensSession")}
+                value={formatTokens(data().totals.tokensPerSession)}
+              />
+              <MetricCard
+                label={i18n.t("model.cacheRatio")}
+                value={`${formatPercent(data().totals.cacheRatio)} ${i18n.t("model.inputTokens")}`}
+              />
+            </div>
+          </>
         )}
       </Show>
     </section>
@@ -1129,12 +1119,12 @@ function ModelPeersSection(props: { data: StatsModelData | null }) {
   )
 }
 
-function MetricCard(props: { label: string; value: string; detail: string; state?: "positive" | "negative" }) {
+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>
+      <Show when={props.detail}>{(detail) => <p>{detail()}</p>}</Show>
     </article>
   )
 }
@@ -1517,8 +1507,9 @@ function formatMoney(value: number) {
   return `$${value.toFixed(value >= 10 ? 0 : 2)}`
 }
 
-function formatCatalogPrice(value: ModelCatalogCost) {
-  return `${formatModelPrice(value.input)} / ${formatModelPrice(value.output)}`
+function formatCatalogUnitPrice(value: number | undefined) {
+  if (value === undefined) return "-"
+  return `${formatModelPrice(value)} / 1M`
 }
 
 function formatModelPrice(value: number) {

+ 55 - 0
packages/stats/app/src/routes/index.css

@@ -3913,6 +3913,50 @@
   color: #b82d35;
 }
 
+[data-page="stats"] [data-slot="model-efficiency-pattern"] {
+  width: 100%;
+  height: 16px;
+  margin-bottom: 40px;
+  overflow: hidden;
+  background: color-mix(in srgb, var(--stats-text) 10%, transparent);
+  mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E");
+  mask-position: center top;
+  mask-repeat: repeat;
+  mask-size: 6px 6px;
+  -webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 6 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 0H2V2H0V0Z' fill='black'/%3E%3C/svg%3E");
+  -webkit-mask-position: center top;
+  -webkit-mask-repeat: repeat;
+  -webkit-mask-size: 6px 6px;
+}
+
+[data-page="stats"] [data-component="model-efficiency-grid"] {
+  display: grid;
+  grid-template-columns: repeat(3, minmax(0, 1fr));
+  gap: 12px;
+}
+
+[data-page="stats"] [data-component="model-efficiency-grid"] [data-component="model-metric"] {
+  display: flex;
+  align-items: flex-start;
+  flex-direction: column;
+  box-sizing: border-box;
+  gap: 0;
+  height: 120px;
+  min-height: 120px;
+  padding: 16px;
+  background: var(--stats-layer);
+}
+
+[data-page="stats"] [data-component="model-efficiency-grid"] [data-component="model-metric"] span {
+  line-height: 14px;
+}
+
+[data-page="stats"] [data-component="model-efficiency-grid"] [data-component="model-metric"] strong {
+  margin-top: 32px;
+  font-size: 28px;
+  line-height: 42px;
+}
+
 [data-page="stats"] [data-section="model-panel"][data-variant="lab-models"] {
   overflow: hidden;
 }
@@ -5699,6 +5743,7 @@
 
   [data-page="stats"] [data-component="model-metric-grid"],
   [data-page="stats"] [data-component="model-metric-grid"][data-variant="dense"],
+  [data-page="stats"] [data-component="model-efficiency-grid"],
   [data-page="stats"] [data-component="lab-model-grid"] {
     grid-template-columns: 1fr 1fr;
   }
@@ -6125,6 +6170,7 @@
 
   [data-page="stats"] [data-component="model-metric-grid"],
   [data-page="stats"] [data-component="model-metric-grid"][data-variant="dense"],
+  [data-page="stats"] [data-component="model-efficiency-grid"],
   [data-page="stats"] [data-component="lab-model-grid"] {
     grid-template-columns: 1fr;
   }
@@ -6133,6 +6179,15 @@
     min-height: 112px;
   }
 
+  [data-page="stats"] [data-component="model-efficiency-grid"] [data-component="model-metric"] {
+    height: 112px;
+    min-height: 112px;
+  }
+
+  [data-page="stats"] [data-component="model-efficiency-grid"] [data-component="model-metric"] strong {
+    margin-top: 24px;
+  }
+
   [data-page="stats"] [data-component="model-usage-chart"] {
     --model-usage-mobile-bar-width: 16px;
     --model-usage-mobile-edge-space: 48px;