@linxiraos/pi-stats 1.1.26 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/CHANGELOG.md +12 -5
  2. package/build.ts +4 -0
  3. package/dist/client/favicon.ico +0 -0
  4. package/dist/client/index.css +1 -1
  5. package/dist/client/index.html +1 -0
  6. package/dist/client/index.js +96 -96
  7. package/dist/types/aggregator.d.ts +4 -1
  8. package/dist/types/client/api.d.ts +5 -1
  9. package/dist/types/client/app/nav.d.ts +5 -2
  10. package/dist/types/client/data/formatters.d.ts +7 -2
  11. package/dist/types/client/data/live.d.ts +16 -11
  12. package/dist/types/client/data/query-store.d.ts +39 -0
  13. package/dist/types/client/data/query.d.ts +10 -5
  14. package/dist/types/client/data/range.d.ts +4 -3
  15. package/dist/types/client/i18n/format.d.ts +9 -0
  16. package/dist/types/client/i18n/index.d.ts +18 -0
  17. package/dist/types/client/i18n/messages/charts.d.ts +8 -0
  18. package/dist/types/client/i18n/messages/common.d.ts +153 -0
  19. package/dist/types/client/i18n/messages/costs.d.ts +92 -0
  20. package/dist/types/client/i18n/messages/en.d.ts +1255 -0
  21. package/dist/types/client/i18n/messages/errors.d.ts +78 -0
  22. package/dist/types/client/i18n/messages/frustration.d.ts +191 -0
  23. package/dist/types/client/i18n/messages/gain.d.ts +54 -0
  24. package/dist/types/client/i18n/messages/models.d.ts +94 -0
  25. package/dist/types/client/i18n/messages/nav.d.ts +46 -0
  26. package/dist/types/client/i18n/messages/overview.d.ts +80 -0
  27. package/dist/types/client/i18n/messages/projects.d.ts +84 -0
  28. package/dist/types/client/i18n/messages/providers.d.ts +142 -0
  29. package/dist/types/client/i18n/messages/requests.d.ts +72 -0
  30. package/dist/types/client/i18n/messages/tools.d.ts +119 -0
  31. package/dist/types/client/i18n/messages/traces.d.ts +144 -0
  32. package/dist/types/client/i18n/messages/ui.d.ts +44 -0
  33. package/dist/types/client/i18n/messages/zh-CN.d.ts +13 -0
  34. package/dist/types/client/i18n/types.d.ts +15 -0
  35. package/dist/types/client/traces/Minimap.d.ts +2 -0
  36. package/dist/types/client/traces/TimelineCanvas.d.ts +29 -8
  37. package/dist/types/client/traces/TranscriptList.d.ts +3 -1
  38. package/dist/types/db.d.ts +14 -21
  39. package/dist/types/embedded-client.d.ts +5 -0
  40. package/dist/types/frustration.d.ts +18 -9
  41. package/dist/types/live.d.ts +5 -3
  42. package/dist/types/locale.d.ts +16 -0
  43. package/dist/types/parser.d.ts +0 -17
  44. package/dist/types/rollup.d.ts +9 -1
  45. package/dist/types/server.d.ts +6 -0
  46. package/dist/types/shared-types.d.ts +7 -0
  47. package/dist/types/trace.d.ts +18 -6
  48. package/dist/types/types.d.ts +13 -1
  49. package/dist/types/user-metrics.d.ts +23 -1
  50. package/package.json +5 -5
  51. package/src/aggregator.ts +31 -11
  52. package/src/client/api.ts +40 -5
  53. package/src/client/app/LiveChip.tsx +14 -13
  54. package/src/client/app/Shell.tsx +19 -13
  55. package/src/client/app/ThemeToggle.tsx +4 -3
  56. package/src/client/app/nav.ts +19 -16
  57. package/src/client/data/formatters.ts +80 -13
  58. package/src/client/data/live.tsx +85 -45
  59. package/src/client/data/query-store.ts +108 -0
  60. package/src/client/data/query.ts +47 -56
  61. package/src/client/data/range.ts +34 -14
  62. package/src/client/data/series.ts +7 -1
  63. package/src/client/favicon.ico +0 -0
  64. package/src/client/i18n/format.ts +28 -0
  65. package/src/client/i18n/index.ts +53 -0
  66. package/src/client/i18n/messages/charts.ts +8 -0
  67. package/src/client/i18n/messages/common.ts +157 -0
  68. package/src/client/i18n/messages/costs.ts +104 -0
  69. package/src/client/i18n/messages/en.ts +40 -0
  70. package/src/client/i18n/messages/errors.ts +80 -0
  71. package/src/client/i18n/messages/frustration.ts +198 -0
  72. package/src/client/i18n/messages/gain.ts +54 -0
  73. package/src/client/i18n/messages/models.ts +94 -0
  74. package/src/client/i18n/messages/nav.ts +46 -0
  75. package/src/client/i18n/messages/overview.ts +90 -0
  76. package/src/client/i18n/messages/projects.ts +84 -0
  77. package/src/client/i18n/messages/providers.ts +165 -0
  78. package/src/client/i18n/messages/requests.ts +73 -0
  79. package/src/client/i18n/messages/tools.ts +120 -0
  80. package/src/client/i18n/messages/traces.ts +160 -0
  81. package/src/client/i18n/messages/ui.ts +46 -0
  82. package/src/client/i18n/messages/zh-CN.ts +43 -0
  83. package/src/client/i18n/types.ts +19 -0
  84. package/src/client/index.tsx +4 -0
  85. package/src/client/routes/CostsRoute.tsx +61 -48
  86. package/src/client/routes/ErrorsRoute.tsx +71 -47
  87. package/src/client/routes/FrustrationRoute.tsx +131 -116
  88. package/src/client/routes/GainRoute.tsx +42 -30
  89. package/src/client/routes/ModelsRoute.tsx +90 -73
  90. package/src/client/routes/OverviewRoute.tsx +109 -63
  91. package/src/client/routes/ProjectsRoute.tsx +73 -46
  92. package/src/client/routes/ProvidersRoute.tsx +151 -114
  93. package/src/client/routes/RequestsRoute.tsx +69 -46
  94. package/src/client/routes/ToolsRoute.tsx +108 -94
  95. package/src/client/routes/TracesRoute.tsx +25 -23
  96. package/src/client/styles.css +5 -2
  97. package/src/client/traces/AggregatesPanel.tsx +19 -8
  98. package/src/client/traces/Minimap.tsx +65 -27
  99. package/src/client/traces/SpanDrawer.tsx +46 -21
  100. package/src/client/traces/SummaryStrip.tsx +27 -11
  101. package/src/client/traces/TimelineCanvas.tsx +245 -116
  102. package/src/client/traces/TraceView.tsx +51 -36
  103. package/src/client/traces/TranscriptList.tsx +20 -11
  104. package/src/client/traces/traces.css +26 -0
  105. package/src/client/ui/JsonBlock.tsx +3 -2
  106. package/src/client/ui/Modal.tsx +3 -2
  107. package/src/client/ui/RequestDrawer.tsx +33 -30
  108. package/src/client/ui/SearchInput.tsx +2 -1
  109. package/src/client/ui/States.tsx +3 -2
  110. package/src/client/ui/Table.tsx +7 -3
  111. package/src/db.ts +150 -135
  112. package/src/embedded-client.ts +9 -0
  113. package/src/frustration.ts +9 -30
  114. package/src/index.ts +2 -1
  115. package/src/live.ts +36 -11
  116. package/src/locale.ts +29 -0
  117. package/src/parser.ts +37 -72
  118. package/src/port-conflict.ts +1 -1
  119. package/src/prompts/judge/frustration.json +19 -0
  120. package/src/rollup.ts +41 -20
  121. package/src/server.ts +67 -27
  122. package/src/shared-types.ts +7 -0
  123. package/src/trace.ts +54 -40
  124. package/src/types.ts +13 -1
  125. package/src/user-metrics.ts +220 -6
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Cross-page vocabulary. Every route derives its column headers and card
3
+ * labels from here when the term matches, so one glossary drives the whole
4
+ * dashboard (Providers is 服务商 everywhere, Tokens is Token everywhere).
5
+ *
6
+ * `shell.*` covers the frame: top bar, live chip, theme toggle.
7
+ */
8
+ export const common = {
9
+ en: {
10
+ // ── shared table / card vocabulary ──────────────────────────────────
11
+ "common.provider": "Provider",
12
+ "common.providers": "Providers",
13
+ "common.model": "Model",
14
+ "common.models": "Models",
15
+ "common.requests": "Requests",
16
+ "common.request": "Request",
17
+ "common.tokens": "Tokens",
18
+ "common.share": "Share",
19
+ "common.cost": "Cost",
20
+ "common.error-rate": "Error rate",
21
+ "common.tokens-per-second": "Tokens/s",
22
+ "common.output-tokens-per-second": "Output tokens/s",
23
+ "common.premium": "Premium",
24
+ "common.premium-requests": "Premium requests",
25
+ "common.duration": "Duration",
26
+ "common.started": "Started",
27
+ "common.request-id": "Request id",
28
+ "common.session-entry": "Session entry",
29
+ "common.entry-id": "Entry id",
30
+ "common.input": "Input",
31
+ "common.output": "Output",
32
+ "common.output-message": "Output message",
33
+ "common.cache-read": "Cache read",
34
+ "common.cache-write": "Cache write",
35
+ "common.project": "Project",
36
+ "common.pages": "Pages",
37
+ "common.filter": "Filter…",
38
+ "common.no-data-in-range": "No data in this range",
39
+ "common.nothing-here-yet": "Nothing here yet",
40
+ "common.show-more": "Show more",
41
+ "common.total": "Total",
42
+ "common.average": "Average",
43
+ "common.unknown": "Unknown",
44
+ "common.root": "(root)",
45
+ "common.other-n": "Other ({count})",
46
+ "common.time-range": "Time range",
47
+ "common.range-1h": "1h",
48
+ "common.range-24h": "24h",
49
+ "common.range-7d": "7d",
50
+ "common.range-30d": "30d",
51
+ "common.range-90d": "90d",
52
+ "common.range-all": "All",
53
+ "common.window-1h": "the last hour",
54
+ "common.window-24h": "the last 24 hours",
55
+ "common.window-7d": "the last 7 days",
56
+ "common.window-30d": "the last 30 days",
57
+ "common.window-90d": "the last 90 days",
58
+ "common.window-all": "all time",
59
+ // ── actions ─────────────────────────────────────────────────────────
60
+ "common.copy": "Copy",
61
+ "common.copied": "Copied",
62
+ "common.close": "Close",
63
+ "common.cancel": "Cancel",
64
+ // ── states ──────────────────────────────────────────────────────────
65
+ "common.aborted": "Aborted",
66
+ "common.failed": "Failed",
67
+ "common.error": "Error",
68
+ // ── frame / live chip ───────────────────────────────────────────────
69
+ "shell.live": "Live",
70
+ "shell.reconnecting": "Reconnecting",
71
+ "shell.syncing": "Syncing",
72
+ "shell.sync-failed": "Sync failed",
73
+ "shell.indexing": "Indexing",
74
+ "shell.lost-connection": "Lost the live connection to the stats server; retrying",
75
+ "shell.ingesting": "Ingesting session files",
76
+ "shell.building-rollups": "Building hourly rollups; older ranges fill in as it goes",
77
+ "shell.sync-now": "Click to sync session files now",
78
+ "shell.just-now": "just now",
79
+ "shell.system-theme": "System theme",
80
+ "shell.dark-theme": "Dark theme",
81
+ "shell.light-theme": "Light theme",
82
+ "shell.range-suffix": "range",
83
+ "shell.jump-hint": "then letter to jump",
84
+ },
85
+ "zh-CN": {
86
+ "common.provider": "服务商",
87
+ "common.providers": "服务商",
88
+ "common.model": "模型",
89
+ "common.models": "模型",
90
+ "common.requests": "请求",
91
+ "common.request": "请求",
92
+ "common.tokens": "Token",
93
+ "common.share": "占比",
94
+ "common.cost": "费用",
95
+ "common.error-rate": "错误率",
96
+ "common.tokens-per-second": "Token/秒",
97
+ "common.output-tokens-per-second": "输出 Token/秒",
98
+ "common.premium": "溢价",
99
+ "common.premium-requests": "溢价请求",
100
+ "common.duration": "耗时",
101
+ "common.started": "开始时间",
102
+ "common.request-id": "请求 ID",
103
+ "common.session-entry": "会话条目",
104
+ "common.entry-id": "条目 ID",
105
+ "common.input": "输入",
106
+ "common.output": "输出",
107
+ "common.output-message": "输出消息",
108
+ "common.cache-read": "缓存读取",
109
+ "common.cache-write": "缓存写入",
110
+ "common.project": "项目",
111
+ "common.pages": "页数",
112
+ "common.filter": "筛选…",
113
+ "common.no-data-in-range": "该时间范围内没有数据",
114
+ "common.nothing-here-yet": "暂无数据",
115
+ "common.show-more": "显示更多",
116
+ "common.total": "合计",
117
+ "common.average": "平均",
118
+ "common.unknown": "未知",
119
+ "common.root": "(根目录)",
120
+ "common.other-n": "其他({count})",
121
+ "common.time-range": "时间范围",
122
+ "common.range-1h": "1 小时",
123
+ "common.range-24h": "24 小时",
124
+ "common.range-7d": "7 天",
125
+ "common.range-30d": "30 天",
126
+ "common.range-90d": "90 天",
127
+ "common.range-all": "全部",
128
+ "common.window-1h": "最近 1 小时",
129
+ "common.window-24h": "最近 24 小时",
130
+ "common.window-7d": "最近 7 天",
131
+ "common.window-30d": "最近 30 天",
132
+ "common.window-90d": "最近 90 天",
133
+ "common.window-all": "全部时间",
134
+ "common.copy": "复制",
135
+ "common.copied": "已复制",
136
+ "common.close": "关闭",
137
+ "common.cancel": "取消",
138
+ "common.aborted": "已中止",
139
+ "common.failed": "失败",
140
+ "common.error": "错误",
141
+ "shell.live": "实时",
142
+ "shell.reconnecting": "重新连接中",
143
+ "shell.syncing": "同步中",
144
+ "shell.sync-failed": "同步失败",
145
+ "shell.indexing": "建立索引",
146
+ "shell.lost-connection": "与统计服务的实时连接已断开,正在重试",
147
+ "shell.ingesting": "正在导入会话文件",
148
+ "shell.building-rollups": "正在构建小时汇总;更早的时间范围会逐步补全",
149
+ "shell.sync-now": "点击立即同步会话文件",
150
+ "shell.just-now": "刚刚",
151
+ "shell.system-theme": "跟随系统",
152
+ "shell.dark-theme": "深色主题",
153
+ "shell.light-theme": "浅色主题",
154
+ "shell.range-suffix": "范围",
155
+ "shell.jump-hint": "再按字母跳转",
156
+ },
157
+ } as const;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Costs copy. Keys are slugs (`costs.<name>`); the English string is the
3
+ * source of truth and `zh-CN` overrides it.
4
+ */
5
+ export const costs = {
6
+ en: {
7
+ // ── page header / empty state ───────────────────────────────────────
8
+ "costs.title": "Costs",
9
+ "costs.description":
10
+ "What {window} of usage would cost at public API rates. Subscription usage without a public price is counted as unpriced, not free.",
11
+ "costs.no-usage-in-range": "No usage in this range",
12
+ "costs.empty-hint": "Pick a wider range in the top bar.",
13
+ // ── daily estimate card ─────────────────────────────────────────────
14
+ "costs.daily-estimate": "Daily estimate",
15
+ "costs.daily-by-model": "Per UTC day, stacked by model",
16
+ "costs.daily-by-component": "Per UTC day, stacked by billing component",
17
+ "costs.split-by": "Split by",
18
+ "costs.by-model": "By model",
19
+ "costs.by-component": "By component",
20
+ "costs.no-priced-usage": "No priced usage in this range",
21
+ "costs.unpriced-requests": "Unpriced requests",
22
+ // ── component breakdown card ────────────────────────────────────────
23
+ "costs.where-it-went": "Where it went",
24
+ "costs.estimate-by-component": "Estimate by billing component",
25
+ "costs.unpriced-note-one": "{count} unpriced subscription request is not included.",
26
+ "costs.unpriced-note-n": "{count} unpriced subscription requests are not included.",
27
+ // ── by model card + table ───────────────────────────────────────────
28
+ "costs.by-model-description": "Estimate per model with its input, output and cache split",
29
+ "costs.estimate": "Estimate",
30
+ "costs.estimate-title": "API-equivalent estimate at public rates",
31
+ "costs.split": "Split",
32
+ "costs.split-title": "Input · output · cache read · cache write",
33
+ "costs.per-request": "Per request",
34
+ "costs.per-request-title": "Estimate ÷ priced requests",
35
+ "costs.unpriced": "Unpriced",
36
+ "costs.unpriced-column-title": "Requests with no public-equivalent price",
37
+ // ── summary stats ───────────────────────────────────────────────────
38
+ "costs.api-equivalent-estimate": "API-equivalent estimate",
39
+ "costs.api-estimate-title": "What this usage would cost at public API rates",
40
+ "costs.requests-n": "{count} requests",
41
+ "costs.average-per-day": "Average per day",
42
+ "costs.average-per-day-title": "Estimate ÷ days with any usage",
43
+ "costs.active-days-one": "over {count} active day",
44
+ "costs.active-days-n": "over {count} active days",
45
+ "costs.top-model": "Top model",
46
+ "costs.top-model-hint": "{cost} · {share} of estimate",
47
+ "costs.nothing-priced-yet": "Nothing priced yet",
48
+ "costs.per-priced-request": "Per priced request",
49
+ "costs.per-priced-request-title": "Estimate ÷ requests that have a public price",
50
+ "costs.priced-n": "{count} priced",
51
+ "costs.unpriced-title": "Subscription usage with no public-equivalent price; excluded from the estimate",
52
+ "costs.unpriced-hint": "excluded from the estimate",
53
+ "costs.all-priced": "all usage priced",
54
+ },
55
+ "zh-CN": {
56
+ // ── page header / empty state ───────────────────────────────────────
57
+ "costs.title": "费用",
58
+ "costs.description":
59
+ "按公开 API 费率计算,{window}的用量需要多少费用。没有公开价格的订阅用量计为未定价,而不是免费。",
60
+ "costs.no-usage-in-range": "此时间范围内没有用量",
61
+ "costs.empty-hint": "请在顶栏选择更宽的时间范围。",
62
+ // ── daily estimate card ─────────────────────────────────────────────
63
+ "costs.daily-estimate": "每日估算",
64
+ "costs.daily-by-model": "按 UTC 日统计,按模型堆叠",
65
+ "costs.daily-by-component": "按 UTC 日统计,按计费组件堆叠",
66
+ "costs.split-by": "拆分依据",
67
+ "costs.by-model": "按模型",
68
+ "costs.by-component": "按组件",
69
+ "costs.no-priced-usage": "此时间范围内没有已定价的用量",
70
+ "costs.unpriced-requests": "未定价请求",
71
+ // ── component breakdown card ────────────────────────────────────────
72
+ "costs.where-it-went": "费用去向",
73
+ "costs.estimate-by-component": "按计费组件估算",
74
+ "costs.unpriced-note-one": "{count} 个未定价的订阅请求未计入。",
75
+ "costs.unpriced-note-n": "{count} 个未定价的订阅请求未计入。",
76
+ // ── by model card + table ───────────────────────────────────────────
77
+ "costs.by-model-description": "按模型估算,并附输入、输出与缓存的拆分",
78
+ "costs.estimate": "估算",
79
+ "costs.estimate-title": "按公开费率计算的 API 等效估算",
80
+ "costs.split": "拆分",
81
+ "costs.split-title": "输入 · 输出 · 缓存读取 · 缓存写入",
82
+ "costs.per-request": "单次请求",
83
+ "costs.per-request-title": "估算值 ÷ 已定价请求数",
84
+ "costs.unpriced": "未定价",
85
+ "costs.unpriced-column-title": "没有公开等效价格的请求",
86
+ // ── summary stats ───────────────────────────────────────────────────
87
+ "costs.api-equivalent-estimate": "API 等效估算",
88
+ "costs.api-estimate-title": "此用量按公开 API 费率计算需要多少费用",
89
+ "costs.requests-n": "{count} 个请求",
90
+ "costs.average-per-day": "日均",
91
+ "costs.average-per-day-title": "估算值 ÷ 有用量的天数",
92
+ "costs.active-days-one": "共 {count} 个活跃日",
93
+ "costs.active-days-n": "共 {count} 个活跃日",
94
+ "costs.top-model": "费用最高的模型",
95
+ "costs.top-model-hint": "{cost} · 占估算值的 {share}",
96
+ "costs.nothing-priced-yet": "暂无已定价用量",
97
+ "costs.per-priced-request": "单个已定价请求",
98
+ "costs.per-priced-request-title": "估算值 ÷ 有公开价格的请求数",
99
+ "costs.priced-n": "已定价 {count} 个",
100
+ "costs.unpriced-title": "没有公开等效价格的订阅用量;不计入估算",
101
+ "costs.unpriced-hint": "不计入估算",
102
+ "costs.all-priced": "全部用量均已定价",
103
+ },
104
+ } as const;
@@ -0,0 +1,40 @@
1
+ import { nav } from "./nav";
2
+ import { common } from "./common";
3
+ import { overview } from "./overview";
4
+ import { models } from "./models";
5
+ import { providers } from "./providers";
6
+ import { costs } from "./costs";
7
+ import { requests } from "./requests";
8
+ import { errors } from "./errors";
9
+ import { traces } from "./traces";
10
+ import { tools } from "./tools";
11
+ import { frustration } from "./frustration";
12
+ import { projects } from "./projects";
13
+ import { gain } from "./gain";
14
+ import { ui } from "./ui";
15
+ import { charts } from "./charts";
16
+
17
+ /** Every English message: the source of truth for the key set. */
18
+ export const enMessages = {
19
+ ...nav.en,
20
+ ...common.en,
21
+ ...overview.en,
22
+ ...models.en,
23
+ ...providers.en,
24
+ ...costs.en,
25
+ ...requests.en,
26
+ ...errors.en,
27
+ ...traces.en,
28
+ ...tools.en,
29
+ ...frustration.en,
30
+ ...projects.en,
31
+ ...gain.en,
32
+ ...ui.en,
33
+ ...charts.en,
34
+ } as const;
35
+
36
+ /** Keys the dashboard may translate. A `t("…")` call with an unknown key is a type error. */
37
+ export type MessageKey = keyof typeof enMessages;
38
+
39
+ /** The English locale pack, as the registry consumes it. */
40
+ export const enLocale = { id: "en", messages: enMessages } as const;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Errors copy. Keys are slugs (`errors.<name>`); the English string is the
3
+ * source of truth and `zh-CN` overrides it.
4
+ */
5
+ export const errors = {
6
+ en: {
7
+ "errors.title": "Errors",
8
+ "errors.description":
9
+ "Failed model requests in {window}, grouped by error signature. Open a failure for its full payload.",
10
+ "errors.footer": "Showing the latest {count} failures in {window}; older ones are not loaded.",
11
+ "errors.loading": "Loading…",
12
+ "errors.load-latest": "Load latest {count}",
13
+ "errors.failures": "Failures",
14
+ "errors.hint-in-window": "in {window}",
15
+ "errors.hint-latest-loaded": "latest {count} loaded",
16
+ "errors.signatures": "Signatures",
17
+ "errors.signatures-hint": "Distinct error messages after normalizing ids and numbers",
18
+ "errors.signatures-top": "top: {count} failures",
19
+ "errors.affected-models": "Affected models",
20
+ "errors.last-failure": "Last failure",
21
+ "errors.none-in-window": "none in {window}",
22
+ "errors.error-signatures": "Error signatures",
23
+ "errors.signatures-card-description":
24
+ "Same message with ids and counters normalized. Select one to filter the failures below.",
25
+ "errors.empty-no-failures": "No failures in {window}",
26
+ "errors.empty-all-succeeded": "Every request succeeded or was aborted.",
27
+ "errors.by-model": "By model",
28
+ "errors.by-model-description": "Failures per model. Select one to filter.",
29
+ "errors.empty-no-affected-models": "No affected models",
30
+ "errors.loading-failures": "Loading failures…",
31
+ "errors.filtered-of-total": "{shown} of {total} failures, newest first",
32
+ "errors.search-placeholder": "Message, model or project",
33
+ "errors.no-match": "No failures match",
34
+ "errors.no-match-hint": "Clear the search or the selected filters.",
35
+ "errors.signature": "Signature",
36
+ "errors.more-models": "+{count} more",
37
+ "errors.last-seen": "Last seen",
38
+ "errors.unknown-error": "Unknown error",
39
+ "errors.group-meta": "{count} failures · first {first} · last {last}",
40
+ "errors.open-latest": "Open latest",
41
+ "errors.when": "When",
42
+ "errors.cost-hint": "API-equivalent estimate",
43
+ },
44
+ "zh-CN": {
45
+ "errors.title": "错误",
46
+ "errors.description": "在 {window} 内失败的模型请求,按错误签名分组。打开某条失败可查看完整载荷。",
47
+ "errors.footer": "显示 {window} 内最新的 {count} 个失败;更早的未加载。",
48
+ "errors.loading": "加载中…",
49
+ "errors.load-latest": "加载最新 {count} 条",
50
+ "errors.failures": "失败",
51
+ "errors.hint-in-window": "{window} 内",
52
+ "errors.hint-latest-loaded": "已加载最新 {count} 条",
53
+ "errors.signatures": "签名",
54
+ "errors.signatures-hint": "对 ID 和数字归一化后不同的错误消息",
55
+ "errors.signatures-top": "最多:{count} 个失败",
56
+ "errors.affected-models": "受影响的模型",
57
+ "errors.last-failure": "最近一次失败",
58
+ "errors.none-in-window": "{window} 内没有失败",
59
+ "errors.error-signatures": "错误签名",
60
+ "errors.signatures-card-description": "同一消息在归一化 ID 和计数后归为一组。选择一项可筛选下方的失败。",
61
+ "errors.empty-no-failures": "{window} 内没有失败",
62
+ "errors.empty-all-succeeded": "所有请求均已成功或被中止。",
63
+ "errors.by-model": "按模型",
64
+ "errors.by-model-description": "各模型的失败数。选择一项可筛选。",
65
+ "errors.empty-no-affected-models": "没有受影响的模型",
66
+ "errors.loading-failures": "正在加载失败…",
67
+ "errors.filtered-of-total": "共 {total} 个失败,显示 {shown} 个,最新在前",
68
+ "errors.search-placeholder": "消息、模型或项目",
69
+ "errors.no-match": "没有匹配的失败",
70
+ "errors.no-match-hint": "清除搜索内容或已选筛选条件。",
71
+ "errors.signature": "签名",
72
+ "errors.more-models": "另有 {count} 个",
73
+ "errors.last-seen": "最近出现",
74
+ "errors.unknown-error": "未知错误",
75
+ "errors.group-meta": "{count} 个失败 · 首次 {first} · 最近 {last}",
76
+ "errors.open-latest": "打开最新",
77
+ "errors.when": "时间",
78
+ "errors.cost-hint": "按 API 价格估算",
79
+ },
80
+ } as const;
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Frustration page copy. Keys are slugs (`frustration.<name>`); the English
3
+ * string is the source of truth and `zh-CN` overrides it.
4
+ *
5
+ * Vocabulary fixed by the page's own model: announced frustration is
6
+ * 不耐烦 (Annoyed), the assistant-aimed subset is 针对助手 (At assistant), and
7
+ * its hostile part is 发怒 (Angry). "Unscored" (未评分) is deliberately not a
8
+ * weaker "not annoyed": it means no lexicon covers that message's language.
9
+ */
10
+ export const frustration = {
11
+ en: {
12
+ "frustration.estimate-note":
13
+ "Identical messages share one verdict, and verdicts are cached, so later runs only pay for new messages. Nothing is spent until you proceed.",
14
+ "frustration.column-model-ids": "Model IDs",
15
+ "frustration.column-messages": "Messages",
16
+ "frustration.column-judged": "Judged",
17
+ "frustration.model-versions": "Model versions",
18
+ "frustration.no-model-match": "No model versions match the filters",
19
+ "frustration.small-models-title": "Models with fewer than {count} messages have noisy rates",
20
+ "frustration.hide-regex-title": "Hide models with under {share}% judged messages (rates from regex signals)",
21
+ "frustration.hatch-title": "Under {share}% of these messages are judged; their rates come from regex signals",
22
+ "frustration.regex-badge": "regex",
23
+ "frustration.legend-annoyed-any": "Annoyed, any target",
24
+ "frustration.table-description": "Rates are shares of user messages; rows follow the chart filters.",
25
+ "frustration.running": "Running",
26
+ "frustration.estimating": "Estimating cost…",
27
+ "frustration.failed-n": "{count} failed",
28
+ "frustration.cost-so-far": "cost so far",
29
+ "frustration.elapsed": "elapsed {time}",
30
+ "frustration.in-flight": "{count} in flight",
31
+ "frustration.messages-progress": "{done}/{total} messages",
32
+ "frustration.failed-suffix": ", {count} failed",
33
+ "frustration.judge-with": " with {judge}",
34
+ "frustration.in-time": " in {time}",
35
+ "frustration.run-done-line": "Last run judged {progress}{failed}{judge}{elapsed} for {cost}.",
36
+ "frustration.run-cancelled-line": "Last run cancelled after {progress}{failed}; spent {cost}.",
37
+ "frustration.run-failed-line": "Last run failed after {progress}{failed}: {error}. Spent {cost}.",
38
+ "frustration.title": "Frustration",
39
+ "frustration.description": "How often your messages sound annoyed, per model version, in {window}.",
40
+ "frustration.user-messages": "User messages",
41
+ "frustration.user-messages-title": "User messages with prose in range",
42
+ "frustration.judge-coverage": "Judge coverage",
43
+ "frustration.judge-coverage-title": "Messages with a cached judge verdict; the rest use the regex lexicons",
44
+ "frustration.coverage-hint": "{judged} judged · {regex} regex",
45
+ "frustration.coverage-unscored": " · {count} unscored (no lexicon)",
46
+ "frustration.unscored-line": "Messages in a language without a lexicon: {count} — unscored, not calm.",
47
+ "frustration.annoyed": "Annoyed",
48
+ "frustration.annoyed-title": "Messages annoyed at any target",
49
+ "frustration.at-assistant": "At assistant",
50
+ "frustration.at-assistant-title": "Messages annoyed at the assistant",
51
+ "frustration.angry": "Angry",
52
+ "frustration.angry-title": "Messages angry or hostile toward the assistant",
53
+ "frustration.messages-n": "{count} messages",
54
+ "frustration.judge-panel": "Judge classification",
55
+ "frustration.judge-description":
56
+ "Cached LLM verdicts replace the regex lexicons; unjudged messages fall back to regex signals. Messages in a language no lexicon covers are reported as unscored rather than counted as calm.",
57
+ "frustration.classify": "Classify with judge",
58
+ "frustration.proceed": "Proceed",
59
+ "frustration.starting": "Starting…",
60
+ "frustration.judge-running": "A judge run is in progress",
61
+ "frustration.quote-title": "Quote the cost of judging every unjudged message in range",
62
+ "frustration.estimated-cost": "Estimated cost",
63
+ "frustration.unjudged-messages": "Unjudged unique messages",
64
+ "frustration.prose-chars": "Prose characters",
65
+ "frustration.input-tokens": "Estimated input tokens",
66
+ "frustration.judge-model": "Judge",
67
+ "frustration.all-judged": "Everything in this range is already judged. There is nothing left to classify.",
68
+ "frustration.no-run-yet":
69
+ "No judge run yet. Classify the unjudged messages to replace regex estimates with verdicts.",
70
+ "frustration.last-run-done": "Last run done",
71
+ "frustration.last-run-cancelled": "Last run cancelled",
72
+ "frustration.last-run-failed": "Last run failed",
73
+ "frustration.not-run-yet": "Not run yet",
74
+ "frustration.regex-only": "Regex only",
75
+ "frustration.cancel-run": "Cancel run",
76
+ "frustration.cancelling": "Cancelling…",
77
+ "frustration.run-judged": "Last run judged {progress}",
78
+ "frustration.run-cancelled-after": "Last run cancelled after {progress}",
79
+ "frustration.run-failed-after": "Last run failed after {progress}",
80
+ "frustration.by-model": "Frustration by model version",
81
+ "frustration.by-model-description":
82
+ "Share of user messages per model version. Each bar reaches the annoyed rate; the subsets aimed at the assistant stack inside it.",
83
+ "frustration.all-classes": "All",
84
+ "frustration.every-model-class": "Every model class",
85
+ "frustration.model-class": "Model class",
86
+ "frustration.include-small": "Include < {count} messages",
87
+ "frustration.unclassified": "unclassified",
88
+ "frustration.legend-angry": "Angry at assistant",
89
+ "frustration.legend-assistant": "At assistant, not angry",
90
+ "frustration.legend-other": "Annoyed at other targets",
91
+ "frustration.legend-trend": "At assistant trend",
92
+ "frustration.legend-hatched": "Hatched: mostly regex fallback",
93
+ "frustration.legend-hatched-run": "Hatched: mostly regex fallback (run the judge)",
94
+ "frustration.legend-hue": "Hue = model family",
95
+ "frustration.no-messages": "No user messages in this range",
96
+ "frustration.try-longer-range": "Try a longer range.",
97
+ "frustration.mostly-regex": "Mostly regex fallback: run the judge for accurate rates",
98
+ "frustration.row-mostly-regex": " · mostly regex fallback",
99
+ "frustration.stack-relation": "annoyed ⊇ atAssistant ⊇ angry",
100
+ "frustration.unknown-error": "unknown error",
101
+ "frustration.cancelled": "Cancelled",
102
+ "frustration.failed": "Failed",
103
+ "frustration.done": "Done",
104
+ },
105
+ "zh-CN": {
106
+ "frustration.estimate-note":
107
+ "内容相同的消息共用一份结论,且结论会被缓存,因此后续运行只为新消息付费。在你开始之前不会产生任何费用。",
108
+ "frustration.column-model-ids": "模型 ID",
109
+ "frustration.column-messages": "消息数",
110
+ "frustration.column-judged": "已评判",
111
+ "frustration.model-versions": "模型版本",
112
+ "frustration.no-model-match": "没有符合筛选条件的模型版本",
113
+ "frustration.small-models-title": "消息少于 {count} 条的模型比例噪声较大",
114
+ "frustration.hide-regex-title": "隐藏已评判比例低于 {share}% 的模型(其比例来自正则信号)",
115
+ "frustration.hatch-title": "这些消息中已评判比例低于 {share}%,其比例来自正则信号",
116
+ "frustration.regex-badge": "正则",
117
+ "frustration.legend-annoyed-any": "不耐烦(任意对象)",
118
+ "frustration.table-description": "比例为占用户消息的百分比;行跟随图表筛选。",
119
+ "frustration.running": "运行中",
120
+ "frustration.estimating": "正在估算费用…",
121
+ "frustration.failed-n": "{count} 条失败",
122
+ "frustration.cost-so-far": "当前费用",
123
+ "frustration.elapsed": "已用时 {time}",
124
+ "frustration.in-flight": "并发 {count}",
125
+ "frustration.messages-progress": "{done}/{total} 条消息",
126
+ "frustration.failed-suffix": ",{count} 条失败",
127
+ "frustration.judge-with": ",评判模型 {judge}",
128
+ "frustration.in-time": ",用时 {time}",
129
+ "frustration.run-done-line": "上次运行评判了 {progress}{failed}{judge}{elapsed},花费 {cost}。",
130
+ "frustration.run-cancelled-line": "上次运行在 {progress}{failed} 后取消,花费 {cost}。",
131
+ "frustration.run-failed-line": "上次运行在 {progress}{failed} 后失败:{error}。花费 {cost}。",
132
+ "frustration.title": "情绪压力",
133
+ "frustration.description": "按模型版本统计你的消息有多不耐烦({window})。",
134
+ "frustration.user-messages": "用户消息",
135
+ "frustration.user-messages-title": "该时间范围内的用户消息(含正文)",
136
+ "frustration.judge-coverage": "判据覆盖率",
137
+ "frustration.judge-coverage-title": "已有缓存的评判模型结论的消息;其余使用正则词典",
138
+ "frustration.coverage-hint": "{judged} 条已评判 · {regex} 条正则",
139
+ "frustration.coverage-unscored": " · {count} 条未评分(无词典)",
140
+ "frustration.unscored-line": "缺少对应词典的语言:{count} 条 —— 未评分,不代表平静。",
141
+ "frustration.annoyed": "不耐烦",
142
+ "frustration.annoyed-title": "对任何对象不耐烦的消息",
143
+ "frustration.at-assistant": "针对助手",
144
+ "frustration.at-assistant-title": "对助手不耐烦的消息",
145
+ "frustration.angry": "发怒",
146
+ "frustration.angry-title": "对助手发怒或带有敌意的消息",
147
+ "frustration.messages-n": "{count} 条消息",
148
+ "frustration.judge-panel": "评判模型分类",
149
+ "frustration.judge-description":
150
+ "缓存的模型结论会取代正则词典;未评判的消息回退到正则信号。缺少词典的语言显示为未评分,而不是判定为平静。",
151
+ "frustration.classify": "用评判模型分类",
152
+ "frustration.proceed": "开始",
153
+ "frustration.starting": "正在开始…",
154
+ "frustration.judge-running": "评判任务正在进行",
155
+ "frustration.quote-title": "估算对该范围内所有未评判消息进行判定的成本",
156
+ "frustration.estimated-cost": "预估费用",
157
+ "frustration.unjudged-messages": "未评判的唯一消息",
158
+ "frustration.prose-chars": "正文字符数",
159
+ "frustration.input-tokens": "预估输入 Token",
160
+ "frustration.judge-model": "评判模型",
161
+ "frustration.all-judged": "该范围内已全部评判,没有需要分类的消息。",
162
+ "frustration.no-run-yet": "尚未运行评判。对未评判的消息分类,即可用结论取代正则估算。",
163
+ "frustration.last-run-done": "上次运行完成",
164
+ "frustration.last-run-cancelled": "上次运行已取消",
165
+ "frustration.last-run-failed": "上次运行失败",
166
+ "frustration.not-run-yet": "尚未运行",
167
+ "frustration.regex-only": "仅正则",
168
+ "frustration.cancel-run": "取消运行",
169
+ "frustration.cancelling": "正在取消…",
170
+ "frustration.run-judged": "上次运行已评判 {progress}",
171
+ "frustration.run-cancelled-after": "上次运行在 {progress} 后取消",
172
+ "frustration.run-failed-after": "上次运行在 {progress} 后失败",
173
+ "frustration.by-model": "按模型版本的情绪压力",
174
+ "frustration.by-model-description":
175
+ "各模型版本中用户消息的占比。每根柱子到达不耐烦比例,其中针对助手的子集叠在其内部。",
176
+ "frustration.all-classes": "全部",
177
+ "frustration.every-model-class": "所有模型类别",
178
+ "frustration.model-class": "模型类别",
179
+ "frustration.include-small": "包含少于 {count} 条消息",
180
+ "frustration.unclassified": "未分类",
181
+ "frustration.legend-angry": "对助手发怒",
182
+ "frustration.legend-assistant": "针对助手但未发怒",
183
+ "frustration.legend-other": "对其他对象不耐烦",
184
+ "frustration.legend-trend": "针对助手趋势",
185
+ "frustration.legend-hatched": "斜线:多为正则回退",
186
+ "frustration.legend-hatched-run": "斜线:多为正则回退(运行评判)",
187
+ "frustration.legend-hue": "色相 = 模型家族",
188
+ "frustration.no-messages": "该时间范围内没有用户消息",
189
+ "frustration.try-longer-range": "试试更长的时间范围。",
190
+ "frustration.mostly-regex": "多为正则回退:运行评判以获得准确比例",
191
+ "frustration.row-mostly-regex": " · 多为正则回退",
192
+ "frustration.stack-relation": "不耐烦 ⊇ 针对助手 ⊇ 发怒",
193
+ "frustration.unknown-error": "未知错误",
194
+ "frustration.cancelled": "已取消",
195
+ "frustration.failed": "失败",
196
+ "frustration.done": "完成",
197
+ },
198
+ } as const;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Gain copy. Keys are slugs (`gain.<name>`); the English string is the
3
+ * source of truth and `zh-CN` overrides it.
4
+ */
5
+ export const gain = {
6
+ en: {
7
+ "gain.title": "Gain",
8
+ "gain.description": "Tokens snapcompact kept out of context in {window}.",
9
+ "gain.description-scoped": "Tokens snapcompact kept out of context for {project} in {window}.",
10
+ "gain.all-projects": "All projects",
11
+ "gain.empty-title": "No savings recorded in {window}",
12
+ "gain.empty-title-scoped": "No savings recorded for {project} in {window}",
13
+ "gain.empty-hint-all": "Savings appear here once snapcompact compacts tool output.",
14
+ "gain.empty-hint-longer-range": "Try a longer range.",
15
+ "gain.saved-tokens": "Saved tokens",
16
+ "gain.saved-bytes": "Saved bytes",
17
+ "gain.reduction": "Reduction",
18
+ "gain.reduction-title": "Saved bytes ÷ original bytes, when the original size is known",
19
+ "gain.reduction-unknown": "original size not recorded",
20
+ "gain.hits": "Hits",
21
+ "gain.saved-per-hit": "Saved per hit",
22
+ "gain.savings-over-time": "Savings over time",
23
+ "gain.savings-over-time-description": "Tokens saved per UTC day, with the running total",
24
+ "gain.saved-per-day": "Saved per day",
25
+ "gain.cumulative": "Cumulative",
26
+ "gain.by-source": "By source",
27
+ "gain.by-source-description": "Savings per subsystem",
28
+ "gain.source": "Source",
29
+ },
30
+ "zh-CN": {
31
+ "gain.title": "收益",
32
+ "gain.description": "在{window}内被 snapcompact 挡在上下文之外的 Token。",
33
+ "gain.description-scoped": "在{window}内被 snapcompact 为 {project} 挡在上下文之外的 Token。",
34
+ "gain.all-projects": "全部项目",
35
+ "gain.empty-title": "在{window}内没有节省记录",
36
+ "gain.empty-title-scoped": "在{window}内没有 {project} 的节省记录",
37
+ "gain.empty-hint-all": "snapcompact 压缩工具输出后,节省量会显示在这里。",
38
+ "gain.empty-hint-longer-range": "请尝试更长的时间范围。",
39
+ "gain.saved-tokens": "节省的 Token",
40
+ "gain.saved-bytes": "节省的字节",
41
+ "gain.reduction": "节省比例",
42
+ "gain.reduction-title": "原始大小已知时,节省字节 ÷ 原始字节",
43
+ "gain.reduction-unknown": "未记录原始大小",
44
+ "gain.hits": "命中次数",
45
+ "gain.saved-per-hit": "每次命中节省",
46
+ "gain.savings-over-time": "节省趋势",
47
+ "gain.savings-over-time-description": "每个 UTC 日节省的 Token,以及累计总量",
48
+ "gain.saved-per-day": "每日节省",
49
+ "gain.cumulative": "累计",
50
+ "gain.by-source": "按来源",
51
+ "gain.by-source-description": "各子系统节省量",
52
+ "gain.source": "来源",
53
+ },
54
+ } as const;