niceeval 0.5.4 → 0.6.1

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 (337) hide show
  1. package/INDEX.md +55 -0
  2. package/dist/agents/types.d.ts +321 -0
  3. package/dist/context/types.d.ts +335 -0
  4. package/dist/i18n/core.d.ts +11 -0
  5. package/dist/i18n/en.d.ts +181 -0
  6. package/dist/i18n/index.d.ts +5 -0
  7. package/dist/i18n/zh-CN.d.ts +183 -0
  8. package/dist/o11y/otlp/parse.d.ts +3 -0
  9. package/dist/o11y/otlp/receiver.d.ts +21 -0
  10. package/dist/o11y/otlp/turn-otel.d.ts +43 -0
  11. package/dist/o11y/types.d.ts +190 -0
  12. package/dist/report/aggregate.d.ts +57 -0
  13. package/dist/report/aggregate.js +212 -0
  14. package/dist/report/built-ins/experiment-comparison.d.ts +1 -0
  15. package/dist/report/built-ins/experiment-comparison.js +13 -0
  16. package/dist/report/built-ins/index.d.ts +1 -0
  17. package/dist/report/built-ins/index.js +2 -0
  18. package/dist/report/components.d.ts +180 -0
  19. package/dist/report/components.js +132 -0
  20. package/dist/report/compute.d.ts +106 -0
  21. package/dist/report/compute.js +600 -0
  22. package/dist/report/flag.d.ts +6 -0
  23. package/dist/report/flag.js +8 -0
  24. package/dist/report/format.d.ts +26 -0
  25. package/dist/report/format.js +109 -0
  26. package/dist/report/index.d.ts +19 -0
  27. package/dist/report/index.js +30 -0
  28. package/dist/report/load.d.ts +12 -0
  29. package/dist/report/load.js +56 -0
  30. package/dist/report/locale.d.ts +94 -0
  31. package/dist/report/locale.js +183 -0
  32. package/dist/report/metrics.d.ts +21 -0
  33. package/dist/report/metrics.js +115 -0
  34. package/dist/report/primitives.d.ts +64 -0
  35. package/dist/report/primitives.js +113 -0
  36. package/dist/report/react/AttemptList.d.ts +23 -0
  37. package/dist/report/react/AttemptList.js +19 -0
  38. package/dist/report/react/DeltaTable.d.ts +8 -0
  39. package/dist/report/react/DeltaTable.js +24 -0
  40. package/dist/report/react/EvalList.d.ts +10 -0
  41. package/dist/report/react/EvalList.js +0 -0
  42. package/dist/report/react/ExperimentList.d.ts +11 -0
  43. package/dist/report/react/ExperimentList.js +67 -0
  44. package/dist/report/react/GroupSummary.d.ts +8 -0
  45. package/dist/report/react/GroupSummary.js +8 -0
  46. package/dist/report/react/MetricBars.d.ts +10 -0
  47. package/dist/report/react/MetricBars.js +48 -0
  48. package/dist/report/react/MetricLine.d.ts +9 -0
  49. package/dist/report/react/MetricLine.js +68 -0
  50. package/dist/report/react/MetricMatrix.d.ts +10 -0
  51. package/dist/report/react/MetricMatrix.js +23 -0
  52. package/dist/report/react/MetricScatter.d.ts +9 -0
  53. package/dist/report/react/MetricScatter.js +96 -0
  54. package/dist/report/react/MetricTable.d.ts +11 -0
  55. package/dist/report/react/MetricTable.js +26 -0
  56. package/dist/report/react/RunOverview.d.ts +8 -0
  57. package/dist/report/react/RunOverview.js +12 -0
  58. package/dist/report/react/Scoreboard.d.ts +8 -0
  59. package/dist/report/react/Scoreboard.js +26 -0
  60. package/dist/report/react/cell.d.ts +9 -0
  61. package/dist/report/react/cell.js +9 -0
  62. package/dist/report/react/chart-math.d.ts +14 -0
  63. package/dist/report/react/chart-math.js +68 -0
  64. package/dist/report/react/colors.d.ts +14 -0
  65. package/dist/report/react/colors.js +45 -0
  66. package/dist/report/react/fixtures.d.ts +19 -0
  67. package/dist/report/react/fixtures.js +398 -0
  68. package/dist/report/react/format.d.ts +3 -0
  69. package/dist/report/react/format.js +7 -0
  70. package/dist/report/react/index.d.ts +6 -0
  71. package/dist/report/react/index.js +14 -0
  72. package/dist/report/report.d.ts +24 -0
  73. package/dist/report/report.js +49 -0
  74. package/dist/report/text/faces.d.ts +29 -0
  75. package/dist/report/text/faces.js +541 -0
  76. package/dist/report/text/layout.d.ts +75 -0
  77. package/dist/report/text/layout.js +173 -0
  78. package/dist/report/text/plot.d.ts +37 -0
  79. package/dist/report/text/plot.js +81 -0
  80. package/dist/report/text/table.d.ts +8 -0
  81. package/dist/report/text/table.js +86 -0
  82. package/dist/report/tree.d.ts +99 -0
  83. package/dist/report/tree.js +228 -0
  84. package/dist/report/types.d.ts +408 -0
  85. package/dist/report/types.js +4 -0
  86. package/dist/report/web.d.ts +15 -0
  87. package/dist/report/web.js +37 -0
  88. package/dist/results/locator.d.ts +75 -0
  89. package/dist/results/locator.js +0 -0
  90. package/dist/results/select.d.ts +45 -0
  91. package/dist/results/select.js +230 -0
  92. package/dist/results/source-hash.d.ts +9 -0
  93. package/dist/results/types.d.ts +204 -0
  94. package/dist/runner/eval-source.d.ts +22 -0
  95. package/dist/runner/fingerprint.d.ts +23 -0
  96. package/dist/runner/types.d.ts +332 -0
  97. package/dist/sandbox/docker-stream.d.ts +24 -0
  98. package/dist/sandbox/docker.d.ts +84 -0
  99. package/dist/sandbox/e2b.d.ts +31 -0
  100. package/dist/sandbox/errors.d.ts +4 -0
  101. package/dist/sandbox/local-files.d.ts +6 -0
  102. package/dist/sandbox/paths.d.ts +4 -0
  103. package/dist/sandbox/registry.d.ts +13 -0
  104. package/dist/sandbox/resolve.d.ts +40 -0
  105. package/dist/sandbox/retry.d.ts +10 -0
  106. package/dist/sandbox/shell.d.ts +11 -0
  107. package/dist/sandbox/source-files.d.ts +13 -0
  108. package/dist/sandbox/types.d.ts +203 -0
  109. package/dist/sandbox/vercel.d.ts +37 -0
  110. package/dist/scoring/types.d.ts +70 -0
  111. package/dist/shared/aggregate.d.ts +29 -0
  112. package/dist/shared/aggregate.js +55 -0
  113. package/dist/shared/types.d.ts +27 -0
  114. package/dist/shared/verdict.d.ts +27 -0
  115. package/dist/shared/verdict.js +34 -0
  116. package/dist/tty-line.d.ts +6 -0
  117. package/dist/types.d.ts +7 -0
  118. package/dist/util.d.ts +22 -0
  119. package/docs-site/zh/concepts/adapter.mdx +7 -7
  120. package/docs-site/zh/concepts/assert.mdx +19 -18
  121. package/docs-site/zh/concepts/evals.mdx +12 -11
  122. package/docs-site/zh/concepts/experiment.mdx +2 -2
  123. package/docs-site/zh/concepts/judge.mdx +9 -9
  124. package/docs-site/zh/concepts/overview.mdx +3 -3
  125. package/docs-site/zh/concepts/tier.mdx +6 -6
  126. package/docs-site/zh/example/claude-code-codex-plugin.mdx +9 -5
  127. package/docs-site/zh/example/claude-code-codex-skill.mdx +12 -7
  128. package/docs-site/zh/guides/agent-feedback-loop.mdx +237 -78
  129. package/docs-site/zh/guides/connect-otel.mdx +3 -3
  130. package/docs-site/zh/guides/connect-your-agent.mdx +2 -2
  131. package/docs-site/zh/guides/custom-reports.mdx +196 -36
  132. package/docs-site/zh/guides/experiments.mdx +7 -1
  133. package/docs-site/zh/guides/fixtures.mdx +4 -2
  134. package/docs-site/zh/guides/official-adapters.mdx +31 -9
  135. package/docs-site/zh/guides/publish-report.mdx +4 -4
  136. package/docs-site/zh/guides/report-components.mdx +194 -55
  137. package/docs-site/zh/guides/reporters.mdx +2 -2
  138. package/docs-site/zh/guides/results-data.mdx +61 -34
  139. package/docs-site/zh/guides/runner.mdx +2 -2
  140. package/docs-site/zh/guides/sandbox-agent.mdx +3 -2
  141. package/docs-site/zh/guides/{sandbox-backends.mdx → sandbox-providers.mdx} +12 -12
  142. package/docs-site/zh/guides/viewing-results.mdx +200 -79
  143. package/docs-site/zh/guides/write-experiment.mdx +9 -1
  144. package/docs-site/zh/guides/write-send.mdx +4 -3
  145. package/docs-site/zh/index.mdx +4 -3
  146. package/docs-site/zh/introduction.mdx +2 -1
  147. package/docs-site/zh/quickstart.mdx +5 -3
  148. package/docs-site/zh/reference/builtin-agents.mdx +38 -13
  149. package/docs-site/zh/reference/capabilities.mdx +1 -1
  150. package/docs-site/zh/reference/cli.mdx +36 -13
  151. package/docs-site/zh/reference/define-agent.mdx +15 -13
  152. package/docs-site/zh/reference/define-config.mdx +1 -1
  153. package/docs-site/zh/reference/define-eval.mdx +3 -3
  154. package/docs-site/zh/reference/events.mdx +9 -3
  155. package/docs-site/zh/reference/expect.mdx +13 -8
  156. package/package.json +10 -6
  157. package/src/agents/bub.ts +129 -42
  158. package/src/agents/claude-code.test.ts +152 -0
  159. package/src/agents/claude-code.ts +127 -13
  160. package/src/agents/codex.test.ts +192 -0
  161. package/src/agents/codex.ts +151 -12
  162. package/src/agents/index.ts +9 -3
  163. package/src/agents/manifest.ts +41 -0
  164. package/src/agents/plugin-config.test.ts +94 -0
  165. package/src/agents/shared.ts +33 -2
  166. package/src/agents/skills.test.ts +214 -0
  167. package/src/agents/skills.ts +304 -0
  168. package/src/agents/streaming.ts +5 -5
  169. package/src/agents/types.ts +59 -3
  170. package/src/cli.ts +74 -47
  171. package/src/context/context.test.ts +20 -2
  172. package/src/context/control-flow.ts +1 -1
  173. package/src/context/session.test.ts +92 -1
  174. package/src/context/session.ts +23 -1
  175. package/src/context/types.ts +4 -4
  176. package/src/define.test.ts +8 -8
  177. package/src/define.ts +11 -11
  178. package/src/expect/index.ts +5 -5
  179. package/src/i18n/en.ts +33 -15
  180. package/src/i18n/zh-CN.ts +37 -21
  181. package/src/index.ts +1 -1
  182. package/src/o11y/execution-tree.test.ts +452 -0
  183. package/src/o11y/execution-tree.ts +367 -0
  184. package/src/o11y/otlp/receiver.ts +1 -1
  185. package/src/o11y/otlp/select.ts +7 -3
  186. package/src/o11y/parsers/claude-code.test.ts +175 -0
  187. package/src/o11y/parsers/claude-code.ts +32 -0
  188. package/src/o11y/types.ts +6 -1
  189. package/src/report/aggregate.ts +26 -20
  190. package/src/report/built-in-user-parity.test.tsx +640 -0
  191. package/src/report/built-ins/experiment-comparison.tsx +19 -0
  192. package/src/report/built-ins/index.ts +2 -0
  193. package/src/report/components.tsx +225 -50
  194. package/src/report/compute.ts +335 -54
  195. package/src/report/dual-render.test.tsx +1129 -0
  196. package/src/report/flag.ts +1 -1
  197. package/src/report/format.ts +35 -2
  198. package/src/report/index.ts +47 -14
  199. package/src/report/load.ts +1 -1
  200. package/src/report/locale.ts +229 -0
  201. package/src/report/metrics.ts +39 -15
  202. package/src/report/primitives.tsx +100 -2
  203. package/src/report/react/AttemptList.tsx +113 -0
  204. package/src/report/react/DeltaTable.tsx +9 -6
  205. package/src/report/react/EvalList.tsx +0 -0
  206. package/src/report/react/ExperimentList.tsx +220 -0
  207. package/src/report/react/GroupSummary.tsx +66 -0
  208. package/src/report/react/MetricBars.tsx +24 -13
  209. package/src/report/react/MetricLine.tsx +21 -21
  210. package/src/report/react/MetricMatrix.tsx +8 -4
  211. package/src/report/react/MetricScatter.tsx +126 -96
  212. package/src/report/react/MetricTable.tsx +115 -15
  213. package/src/report/react/RunOverview.tsx +48 -26
  214. package/src/report/react/Scoreboard.tsx +15 -7
  215. package/src/report/react/cell.tsx +18 -9
  216. package/src/{view/app/lib/chart.ts → report/react/chart-math.ts} +2 -6
  217. package/src/report/react/colors.ts +24 -15
  218. package/src/report/react/enhance.js +168 -0
  219. package/src/report/react/fixtures.ts +169 -34
  220. package/src/report/react/format.ts +1 -1
  221. package/src/report/react/index.tsx +21 -6
  222. package/src/report/react/render.test.tsx +180 -31
  223. package/src/report/react/styles.css +432 -213
  224. package/src/report/report.test.ts +398 -94
  225. package/src/report/report.ts +29 -11
  226. package/src/report/text/faces.ts +344 -96
  227. package/src/report/text/layout.ts +76 -11
  228. package/src/report/text/table.ts +98 -0
  229. package/src/report/tree.ts +103 -20
  230. package/src/report/types.ts +184 -25
  231. package/src/report/web.ts +46 -19
  232. package/src/results/annotated-source.test.ts +146 -0
  233. package/src/results/annotated-source.ts +128 -0
  234. package/src/results/attempt-evidence.test.ts +158 -0
  235. package/src/results/attempt-evidence.ts +158 -0
  236. package/src/results/attempt-source.ts +48 -0
  237. package/src/results/copy.ts +154 -139
  238. package/src/results/format.ts +54 -47
  239. package/src/results/host-equivalence.test.ts +527 -0
  240. package/src/results/index.ts +56 -14
  241. package/src/results/locator.test.ts +198 -0
  242. package/src/results/locator.ts +0 -0
  243. package/src/results/open.ts +392 -175
  244. package/src/results/results.test.ts +943 -494
  245. package/src/results/select.ts +129 -23
  246. package/src/results/skipped-notice.ts +0 -0
  247. package/src/results/source-hash.ts +28 -0
  248. package/src/results/types.ts +98 -62
  249. package/src/results/writer.ts +304 -176
  250. package/src/runner/attempt.test.ts +170 -0
  251. package/src/runner/attempt.ts +48 -13
  252. package/src/runner/discover.test.ts +65 -0
  253. package/src/runner/discover.ts +6 -2
  254. package/src/runner/eval-source.test.ts +121 -0
  255. package/src/runner/eval-source.ts +45 -0
  256. package/src/runner/fingerprint.ts +54 -1
  257. package/src/runner/remote-sandbox.ts +1 -1
  258. package/src/runner/report.test.ts +6 -8
  259. package/src/runner/report.ts +5 -4
  260. package/src/runner/reporters/artifacts.ts +27 -41
  261. package/src/runner/reporters/braintrust.test.ts +3 -3
  262. package/src/runner/reporters/braintrust.ts +1 -1
  263. package/src/runner/reporters/console.ts +6 -6
  264. package/src/runner/reporters/json.ts +4 -4
  265. package/src/runner/reporters/live.test.ts +56 -0
  266. package/src/runner/reporters/live.ts +36 -10
  267. package/src/runner/reporters/quiet.test.ts +66 -0
  268. package/src/runner/reporters/quiet.ts +49 -0
  269. package/src/runner/reporters/shared.ts +5 -5
  270. package/src/runner/reporters/table.ts +17 -17
  271. package/src/runner/run.ts +61 -102
  272. package/src/runner/types.ts +43 -32
  273. package/src/sandbox/checkpoint.ts +1 -1
  274. package/src/sandbox/docker.ts +15 -2
  275. package/src/sandbox/e2b.ts +11 -5
  276. package/src/sandbox/errors.ts +13 -0
  277. package/src/sandbox/index.ts +3 -3
  278. package/src/sandbox/local-files.ts +1 -1
  279. package/src/sandbox/registry.ts +6 -5
  280. package/src/sandbox/resolve.ts +38 -23
  281. package/src/sandbox/retry.test.ts +68 -0
  282. package/src/sandbox/retry.ts +44 -0
  283. package/src/sandbox/types.ts +24 -24
  284. package/src/sandbox/vercel.ts +17 -3
  285. package/src/scoring/collector.ts +3 -3
  286. package/src/scoring/judge.ts +2 -2
  287. package/src/scoring/scoped.ts +13 -1
  288. package/src/scoring/types.ts +7 -7
  289. package/src/scoring/verdict.ts +4 -4
  290. package/src/shared/aggregate.ts +23 -3
  291. package/src/shared/types.ts +2 -2
  292. package/src/shared/{outcome.ts → verdict.ts} +15 -15
  293. package/src/show/compose.ts +12 -132
  294. package/src/show/index.ts +119 -95
  295. package/src/show/render.ts +462 -159
  296. package/src/show/show.test.ts +471 -253
  297. package/src/source-loc.ts +1 -1
  298. package/src/tty-line.ts +32 -0
  299. package/src/types.ts +2 -2
  300. package/src/util.test.ts +21 -1
  301. package/src/util.ts +5 -1
  302. package/src/view/app/App.tsx +33 -198
  303. package/src/view/app/components/AttemptModal.tsx +6 -6
  304. package/src/view/app/components/CodeView.tsx +2 -2
  305. package/src/view/app/components/CopyControls.tsx +32 -36
  306. package/src/view/app/components/SkippedRunsBanner.tsx +140 -0
  307. package/src/view/app/components/ui/badge.tsx +1 -1
  308. package/src/view/app/components/ui/dialog.tsx +2 -2
  309. package/src/view/app/i18n.ts +40 -31
  310. package/src/view/app/lib/artifact-url.ts +1 -1
  311. package/src/view/app/lib/attempt-route.test.ts +37 -47
  312. package/src/view/app/lib/attempt-route.ts +26 -26
  313. package/src/view/app/lib/rows.ts +9 -112
  314. package/src/view/app/lib/verdict.ts +25 -0
  315. package/src/view/app/main.tsx +9 -12
  316. package/src/view/app/pages/RunsPage.tsx +4 -4
  317. package/src/view/app/pages/TracesPage.tsx +3 -3
  318. package/src/view/app/shared.ts +0 -1
  319. package/src/view/app/types.ts +6 -51
  320. package/src/view/artifact-serving.test.ts +136 -0
  321. package/src/view/client-dist/app.css +1 -1
  322. package/src/view/client-dist/app.js +21 -21
  323. package/src/view/data.test.ts +214 -143
  324. package/src/view/data.ts +166 -120
  325. package/src/view/index.ts +23 -11
  326. package/src/view/server.ts +52 -17
  327. package/src/view/shared/types.ts +22 -31
  328. package/src/view/styles.css +117 -327
  329. package/src/view/view-report.test.ts +146 -98
  330. package/src/report/default-report.tsx +0 -222
  331. package/src/report/dual-face.test.tsx +0 -527
  332. package/src/report/react/CaseList.tsx +0 -70
  333. package/src/view/app/components/CostScoreChart.tsx +0 -147
  334. package/src/view/app/components/ExperimentTable.tsx +0 -284
  335. package/src/view/app/components/GroupSelector.tsx +0 -61
  336. package/src/view/app/components/primitives.tsx +0 -43
  337. package/src/view/app/lib/outcome.ts +0 -82
@@ -1,71 +1,171 @@
1
- // MetricTable:行维度 × 指标列。
2
- // 行按传入顺序渲染——排序发生在计算侧(table() sort 参数),组件不重排;
3
- // 「点列头重排」是后续的渐进增强,不 hydrate 时以数据侧预排的顺序呈现即完整。
1
+ // MetricTable:行维度 × 指标列。没有实体下钻——要展开到 experiment 的 Eval 或 Eval 的
2
+ // Attempt,用 ExperimentList / EvalList,这个组件只表达任意维度 × 任意指标。
3
+ // 行按传入顺序渲染——排序发生在计算侧(tableData sort 参数),组件不重排;
4
+ // 静态 HTML 以数据侧预排的顺序呈现即完整。web 面额外输出渐进增强的 data 属性:
5
+ // 所有表头带 data-nre-sort、格子带 data-sort-value,enhance.js 在场时点表头
6
+ // 可就地重排(纯展示态交互,不改口径);filter 开时在表格前渲染过滤输入框。
7
+ // meta 在场(rows: "experiment")时补 Model / Agent / Verdicts 列,列序对齐 view
8
+ // 原生榜单:experiment、model、agent、指标列…、verdicts。
4
9
  // 列头以箭头标注 better 方向;samples < total 的格子带覆盖率角标;
5
10
  // 一组全 null 渲染成「缺数据」,绝不画 0(逻辑在 MetricCellView)。
6
11
 
7
12
  import type { ReactElement } from "react";
8
- import type { AttemptRef, TableData } from "../types.ts";
13
+ import type { TableData, TableRowMeta } from "../types.ts";
14
+ import type { AttemptLocator } from "../../results/locator.ts";
15
+ import { DEFAULT_REPORT_LOCALE, localeText, resolveMetricLabel, type ReportLocale } from "../locale.ts";
9
16
  import { MetricCellView } from "./cell.tsx";
10
17
  import { colorClassForKey } from "./colors.ts";
11
18
  import { cx } from "./format.ts";
12
19
 
20
+ /** verdict 计票 pill(「3 passed / 1 failed」):非零判定各一枚,全零如实空。 */
21
+ function VerdictTally({
22
+ verdicts,
23
+ locale,
24
+ }: {
25
+ verdicts: NonNullable<TableRowMeta["verdicts"]>;
26
+ locale: ReportLocale;
27
+ }): ReactElement {
28
+ const kinds = (["passed", "failed", "errored", "skipped"] as const).filter((k) => verdicts[k] > 0);
29
+ return (
30
+ <span className="nre-verdict-tally">
31
+ {kinds.map((kind) => (
32
+ <span key={kind} className={cx("nre-verdict-pill", `nre-verdict-${kind}`)}>
33
+ {verdicts[kind]} {localeText(locale, `verdict.${kind}`)}
34
+ </span>
35
+ ))}
36
+ {kinds.length === 0 && <span className="nre-missing">—</span>}
37
+ </span>
38
+ );
39
+ }
40
+
13
41
  export function MetricTable({
14
42
  data,
15
43
  attemptHref,
44
+ filter,
16
45
  className,
46
+ locale = DEFAULT_REPORT_LOCALE,
17
47
  }: {
18
48
  data: TableData;
19
- attemptHref?: (ref: AttemptRef) => string;
49
+ attemptHref?: (locator: AttemptLocator) => string;
50
+ filter?: boolean;
20
51
  className?: string;
52
+ locale?: ReportLocale;
21
53
  }): ReactElement {
22
- return (
23
- <table className={cx("nre", "nre-metric-table", className)}>
54
+ const hasMeta = data.rows.some((row) => row.meta !== undefined);
55
+ const hasModel = data.rows.some((row) => row.meta?.model !== undefined);
56
+ const hasVerdicts = data.rows.some((row) => row.meta?.verdicts !== undefined);
57
+
58
+ const table = (
59
+ <table className={cx("nre", "nre-metric-table", !filter && className)}>
24
60
  <thead>
25
61
  <tr>
26
- <th scope="col" className="nre-dimension">
62
+ <th scope="col" className="nre-dimension" data-nre-sort="">
27
63
  {data.dimension}
28
64
  </th>
65
+ {hasMeta && hasModel && (
66
+ <th scope="col" className="nre-meta-col" data-nre-sort="">
67
+ {localeText(locale, "table.model")}
68
+ </th>
69
+ )}
70
+ {hasMeta && (
71
+ <th scope="col" className="nre-meta-col" data-nre-sort="">
72
+ {localeText(locale, "table.agent")}
73
+ </th>
74
+ )}
29
75
  {data.columns.map((col) => (
30
- <th scope="col" key={col.key} className="nre-metric-col">
31
- {col.label}
76
+ <th scope="col" key={col.key} className="nre-metric-col" data-nre-sort="">
77
+ {resolveMetricLabel(col.label, locale, col.key)}
32
78
  {col.unit && <span className="nre-unit">({col.unit})</span>}
33
79
  {/* better 方向提示:↑ 越高越好 / ↓ 越低越好 */}
34
80
  {col.better && (
35
81
  <span
36
82
  className="nre-better"
37
- title={col.better === "higher" ? "higher is better" : "lower is better"}
83
+ title={localeText(locale, col.better === "higher" ? "table.higherBetter" : "table.lowerBetter")}
38
84
  >
39
85
  {col.better === "higher" ? "↑" : "↓"}
40
86
  </span>
41
87
  )}
42
88
  </th>
43
89
  ))}
90
+ {hasVerdicts && (
91
+ <th scope="col" className="nre-verdicts-col" data-nre-sort="">
92
+ {localeText(locale, "table.verdicts")}
93
+ </th>
94
+ )}
44
95
  </tr>
45
96
  </thead>
46
97
  <tbody>
47
98
  {data.rows.map((row) => (
48
99
  <tr key={row.key}>
49
100
  {/* 行键 = 维度键(如 agent):稳定散列上色,跨块同键同色 */}
50
- <th scope="row" className={cx("nre-row-key", "nre-key", colorClassForKey(row.key))}>
101
+ <th
102
+ scope="row"
103
+ className={cx("nre-row-key", "nre-key", colorClassForKey(row.key))}
104
+ data-sort-value={row.key}
105
+ >
51
106
  {row.key}
107
+ {/* rows: "experiment" 专属:eval/attempt 数 + 最后运行时间,行键下的一行紧凑摘要
108
+ (旧 ExperimentRow 的 "N eval results · N runs · 最后运行时间") */}
109
+ {row.meta?.evals !== undefined && (
110
+ <div className="nre-row-meta-sub">
111
+ {localeText(locale, "overview.evalsCount", { n: row.meta.evals })}
112
+ {row.meta.attempts !== undefined && row.meta.attempts > row.meta.evals
113
+ ? ` · ${localeText(locale, "overview.attemptsCount", { n: row.meta.attempts })}`
114
+ : ""}
115
+ {row.meta.lastRunAt ? ` · ${localeText(locale, "latestRun", { run: row.meta.lastRunAt })}` : ""}
116
+ </div>
117
+ )}
52
118
  </th>
119
+ {hasMeta && hasModel && (
120
+ <td className="nre-td nre-meta-cell" data-sort-value={row.meta?.model ?? ""}>
121
+ {row.meta?.model ?? <span className="nre-missing">—</span>}
122
+ </td>
123
+ )}
124
+ {hasMeta && (
125
+ <td className="nre-td nre-meta-cell" data-sort-value={row.meta?.agent ?? ""}>
126
+ {row.meta?.agent ?? <span className="nre-missing">—</span>}
127
+ </td>
128
+ )}
53
129
  {data.columns.map((col) => {
54
130
  const cell = row.cells[col.key];
55
131
  return (
56
- <td key={col.key} className="nre-td">
132
+ <td key={col.key} className="nre-td" data-sort-value={cell?.value ?? ""}>
57
133
  {cell ? (
58
- <MetricCellView cell={cell} attemptHref={attemptHref} />
134
+ <MetricCellView cell={cell} attemptHref={attemptHref} locale={locale} />
59
135
  ) : (
60
- // 数据侧没给这个格子(理论上 table() 不会缺列)——按空处理,不编数
136
+ // 数据侧没给这个格子(理论上 tableData 不会缺列)——按空处理,不编数
61
137
  <span className="nre-empty" />
62
138
  )}
63
139
  </td>
64
140
  );
65
141
  })}
142
+ {hasVerdicts && (
143
+ <td className="nre-td nre-verdicts-cell" data-sort-value={row.meta?.verdicts?.passed ?? ""}>
144
+ {row.meta?.verdicts ? (
145
+ <VerdictTally verdicts={row.meta.verdicts} locale={locale} />
146
+ ) : (
147
+ <span className="nre-missing">—</span>
148
+ )}
149
+ </td>
150
+ )}
66
151
  </tr>
67
152
  ))}
68
153
  </tbody>
69
154
  </table>
70
155
  );
156
+
157
+ if (!filter) return table;
158
+ // 过滤输入框渲染在表格前(同一个 wrap 里),enhance.js 经 data-nre-filter 接管;
159
+ // 无 JS 时静默无功能,表格内容依旧完整。
160
+ return (
161
+ <div className={cx("nre", "nre-metric-table-wrap", className)}>
162
+ <input
163
+ className="nre-filter"
164
+ data-nre-filter=""
165
+ type="search"
166
+ placeholder={localeText(locale, "table.filterPlaceholder")}
167
+ />
168
+ {table}
169
+ </div>
170
+ );
71
171
  }
@@ -1,69 +1,91 @@
1
1
  // RunOverview:页头 KPI 条——「这批数据是什么」。
2
2
  // 数字下面标注数据来源(几个快照、何时跑的);warnings 有内容时直接显示在条内,
3
- // 诚实不靠使用者记得渲染(docs/reports.md「第一档」行为清单)。
3
+ // 诚实不靠使用者记得渲染(docs/feature/reports/library.md「RunOverview」行为清单)。
4
4
 
5
5
  import type { ReactElement } from "react";
6
6
  import type { OverviewData } from "../types.ts";
7
- import { MISSING_TEXT, cx, formatDurationMs, formatPercent, formatUSD } from "./format.ts";
7
+ import { DEFAULT_REPORT_LOCALE, localeText, type ReportLocale } from "../locale.ts";
8
+ import { cx, formatDurationMs, formatUSD } from "./format.ts";
8
9
 
9
10
  export function RunOverview({
10
11
  data,
11
12
  className,
13
+ locale = DEFAULT_REPORT_LOCALE,
12
14
  }: {
13
15
  data: OverviewData;
14
16
  className?: string;
17
+ locale?: ReportLocale;
15
18
  }): ReactElement {
16
19
  const { totals } = data;
17
- // 通过率口径与内置 passRate 指标一致:skipped null 不进分母,errored/failed 计 0
18
- const judged = totals.passed + totals.failed + totals.errored;
19
- const passRate = judged > 0 ? formatPercent(totals.passed / judged) : null;
20
+ const missing = <span className="nre-missing">{localeText(locale, "cell.missing")}</span>;
21
+ // 通过率只渲染 computeCell 算好的 MetricCell,不现场重算——覆盖率角标复用
22
+ // MetricCellView 的缺数据/coverage 语义(缺数据 = 显式文案,绝不画 0%)
23
+ const { passRate } = totals;
24
+ const passRateNode =
25
+ passRate.value === null ? (
26
+ missing
27
+ ) : (
28
+ <>
29
+ {passRate.display}
30
+ {passRate.samples < passRate.total && (
31
+ <sup
32
+ className="nre-coverage"
33
+ title={localeText(locale, "cell.coverageTitle", { samples: passRate.samples, total: passRate.total })}
34
+ >
35
+ {passRate.samples}/{passRate.total}
36
+ </sup>
37
+ )}
38
+ </>
39
+ );
20
40
 
21
41
  return (
22
42
  <header className={cx("nre", "nre-overview", className)}>
23
43
  <dl className="nre-kpis">
24
44
  <div className="nre-kpi">
25
- <dt>Snapshots</dt>
45
+ <dt>{localeText(locale, "overview.snapshots")}</dt>
26
46
  <dd>{data.snapshots.length}</dd>
27
47
  </div>
28
48
  <div className="nre-kpi">
29
- <dt>Evals</dt>
49
+ <dt>{localeText(locale, "overview.evals")}</dt>
30
50
  <dd>{totals.evals}</dd>
31
51
  </div>
32
52
  <div className="nre-kpi">
33
- <dt>attempts</dt>
53
+ <dt>{localeText(locale, "overview.attempts")}</dt>
34
54
  <dd>{totals.attempts}</dd>
35
55
  </div>
36
56
  <div className="nre-kpi">
37
- <dt>Pass rate</dt>
38
- <dd>{passRate ?? <span className="nre-missing">{MISSING_TEXT}</span>}</dd>
57
+ <dt>{localeText(locale, "overview.passRate")}</dt>
58
+ <dd>{passRateNode}</dd>
39
59
  </div>
40
60
  <div className="nre-kpi">
41
- <dt>Total cost</dt>
61
+ <dt>{localeText(locale, "overview.totalCost")}</dt>
42
62
  {/* costUSD 全缺 = null:显示缺数据,不编 $0 */}
43
- <dd>
44
- {totals.costUSD === null ? (
45
- <span className="nre-missing">{MISSING_TEXT}</span>
46
- ) : (
47
- formatUSD(totals.costUSD)
48
- )}
49
- </dd>
63
+ <dd>{totals.costUSD === null ? missing : formatUSD(totals.costUSD)}</dd>
50
64
  </div>
51
65
  <div className="nre-kpi">
52
- <dt>Total duration</dt>
66
+ <dt>{localeText(locale, "overview.totalDuration")}</dt>
53
67
  <dd>{formatDurationMs(totals.durationMs)}</dd>
54
68
  </div>
55
69
  </dl>
56
70
 
57
- <p className="nre-outcomes">
58
- <span className="nre-outcome nre-outcome-passed">passed {totals.passed}</span>
59
- <span className="nre-outcome nre-outcome-failed">failed {totals.failed}</span>
60
- <span className="nre-outcome nre-outcome-errored">errored {totals.errored}</span>
61
- <span className="nre-outcome nre-outcome-skipped">skipped {totals.skipped}</span>
71
+ <p className="nre-verdicts">
72
+ <span className="nre-verdict nre-verdict-passed">
73
+ {localeText(locale, "verdict.passed")} {totals.passed}
74
+ </span>
75
+ <span className="nre-verdict nre-verdict-failed">
76
+ {localeText(locale, "verdict.failed")} {totals.failed}
77
+ </span>
78
+ <span className="nre-verdict nre-verdict-errored">
79
+ {localeText(locale, "verdict.errored")} {totals.errored}
80
+ </span>
81
+ <span className="nre-verdict nre-verdict-skipped">
82
+ {localeText(locale, "verdict.skipped")} {totals.skipped}
83
+ </span>
62
84
  </p>
63
85
 
64
86
  {/* 数据来源:哪些快照、何时跑的——报告的数字都从这里来 */}
65
87
  <p className="nre-source">
66
- Source: {data.snapshots.length} snapshots
88
+ {localeText(locale, "overview.source", { n: data.snapshots.length })}
67
89
  {data.snapshots.map((s) => (
68
90
  <span key={`${s.experimentId}@${s.startedAt}`} className="nre-source-snapshot">
69
91
  {s.experimentId}({s.agent}
@@ -72,7 +94,7 @@ export function RunOverview({
72
94
  ))}
73
95
  </p>
74
96
 
75
- {/* 选集的警告(残缺快照等)直接渲染在条内,不静默;结构化字段供程序判断,这里打 message */}
97
+ {/* Selection 的警告(残缺快照等)直接渲染在条内,不静默;结构化字段供程序判断,这里打 message */}
76
98
  {data.warnings.length > 0 && (
77
99
  <ul className="nre-warnings">
78
100
  {data.warnings.map((w, i) => (
@@ -1,18 +1,21 @@
1
1
  // Scoreboard:考试成绩单——总分 + 分科小计。
2
2
  // 固定分母的口径不藏:没跑的题挣 0 分但留在分母里,科目行如实报 missing;
3
- // weights 是「实际生效的权重表」,渲染出来让成绩单可审计(docs/reports.md scoreboard 公式一节)。
3
+ // weights 是「实际生效的权重表」,渲染出来让成绩单可审计(docs/feature/reports/library.md「Scoreboard」)。
4
4
 
5
5
  import type { ReactElement } from "react";
6
6
  import type { ScoreboardData } from "../types.ts";
7
+ import { DEFAULT_REPORT_LOCALE, countText, localeText, type ReportLocale } from "../locale.ts";
7
8
  import { colorClassForKey } from "./colors.ts";
8
9
  import { cx } from "./format.ts";
9
10
 
10
11
  export function Scoreboard({
11
12
  data,
12
13
  className,
14
+ locale = DEFAULT_REPORT_LOCALE,
13
15
  }: {
14
16
  data: ScoreboardData;
15
17
  className?: string;
18
+ locale?: ReportLocale;
16
19
  }): ReactElement {
17
20
  // 科目列 = 各行 subjects 的并集,按首次出现顺序;固定分母下各行本应一致,这里防御性合并
18
21
  const subjectKeys: string[] = [];
@@ -31,7 +34,8 @@ export function Scoreboard({
31
34
  {data.dimension}
32
35
  </th>
33
36
  <th scope="col" className="nre-total-col">
34
- Total<span className="nre-full-marks">/ {data.fullMarks}</span>
37
+ {localeText(locale, "scoreboard.total")}
38
+ <span className="nre-full-marks">/ {data.fullMarks}</span>
35
39
  </th>
36
40
  {subjectKeys.map((key) => (
37
41
  <th scope="col" key={key} className="nre-subject-col">
@@ -55,13 +59,17 @@ export function Scoreboard({
55
59
  <td key={key} className="nre-subject">
56
60
  <span
57
61
  className="nre-subject-score"
58
- title={`${subject.evals} evals, weighted ${subject.earned} of ${subject.possible}`}
62
+ title={localeText(locale, "scoreboard.subjectTitle", {
63
+ evals: subject.evals,
64
+ earned: subject.earned,
65
+ possible: subject.possible,
66
+ })}
59
67
  >
60
68
  {subject.earned}/{subject.possible}
61
69
  </span>
62
70
  {/* 固定分母的如实注脚:没跑、按 0 计的题数 */}
63
71
  {subject.missing > 0 && (
64
- <span className="nre-subject-missing">{subject.missing} {subject.missing === 1 ? "eval" : "evals"} missing, scored 0</span>
72
+ <span className="nre-subject-missing">{countText(locale, "scoreboard.missing", subject.missing)}</span>
65
73
  )}
66
74
  </td>
67
75
  );
@@ -72,15 +80,15 @@ export function Scoreboard({
72
80
  </table>
73
81
  {/* 实际生效的权重表:成绩单可审计 */}
74
82
  <p className="nre-weights">
75
- weights:{" "}
83
+ {localeText(locale, "scoreboard.weights")}{" "}
76
84
  {data.weights.length === 0
77
- ? "all evals ×1"
85
+ ? localeText(locale, "scoreboard.allWeights")
78
86
  : data.weights.map((w) => (
79
87
  <span key={w.prefix} className="nre-weight">
80
88
  {w.prefix} ×{w.weight}
81
89
  </span>
82
90
  ))}
83
- {data.weights.length > 0 && <span className="nre-weight-rest">others ×1</span>}
91
+ {data.weights.length > 0 && <span className="nre-weight-rest">{localeText(locale, "scoreboard.othersWeight")}</span>}
84
92
  </p>
85
93
  </section>
86
94
  );
@@ -3,42 +3,51 @@
3
3
  // 纯渲染、零 hooks;交互只有普通 <a>(下钻由使用者的 attemptHref 决定去处)。
4
4
 
5
5
  import type { ReactElement } from "react";
6
- import type { AttemptRef, MetricCell } from "../types.ts";
7
- import { MISSING_TEXT } from "./format.ts";
6
+ import type { MetricCell } from "../types.ts";
7
+ import type { AttemptLocator } from "../../results/locator.ts";
8
+ import { DEFAULT_REPORT_LOCALE, localeText, type ReportLocale } from "../locale.ts";
8
9
 
9
10
  export function MetricCellView({
10
11
  cell,
11
12
  attemptHref,
13
+ locale = DEFAULT_REPORT_LOCALE,
12
14
  }: {
13
15
  cell: MetricCell;
14
- attemptHref?: (ref: AttemptRef) => string;
16
+ attemptHref?: (locator: AttemptLocator) => string;
17
+ locale?: ReportLocale;
15
18
  }): ReactElement {
16
19
  // 全 null(没有任何有效样本)→ 缺数据文案,绝不画 0;total 仍如实入 title
17
20
  if (cell.value === null) {
18
21
  return (
19
22
  <span className="nre-cell nre-cell-missing">
20
- <span className="nre-missing" title={`0/${cell.total} attempts measurable`}>
21
- {MISSING_TEXT}
23
+ <span className="nre-missing" title={localeText(locale, "cell.noneMeasurableTitle", { total: cell.total })}>
24
+ {localeText(locale, "cell.missing")}
22
25
  </span>
23
26
  </span>
24
27
  );
25
28
  }
26
29
  return (
27
30
  <span className="nre-cell">
28
- <span className="nre-value" title={`${cell.samples}/${cell.total} attempts measured`}>
31
+ <span
32
+ className="nre-value"
33
+ title={localeText(locale, "cell.measuredTitle", { samples: cell.samples, total: cell.total })}
34
+ >
29
35
  {cell.display}
30
36
  </span>
31
37
  {/* samples < total:有 attempt 测不了这个指标,覆盖率角标如实标出 */}
32
38
  {cell.samples < cell.total && (
33
- <sup className="nre-coverage" title={`coverage ${cell.samples}/${cell.total}: this metric is null for the remaining attempts`}>
39
+ <sup
40
+ className="nre-coverage"
41
+ title={localeText(locale, "cell.coverageTitle", { samples: cell.samples, total: cell.total })}
42
+ >
34
43
  {cell.samples}/{cell.total}
35
44
  </sup>
36
45
  )}
37
46
  {/* refs + attemptHref:格子可点,「给我看那次 attempt」就在手边 */}
38
47
  {attemptHref && cell.refs && cell.refs.length > 0 && (
39
48
  <span className="nre-refs">
40
- {cell.refs.map((ref, i) => (
41
- <a key={`${ref.run}:${ref.result}`} className="nre-ref" href={attemptHref(ref)}>
49
+ {cell.refs.map((locator, i) => (
50
+ <a key={locator} className="nre-ref" href={attemptHref(locator)}>
42
51
  #{i + 1}
43
52
  </a>
44
53
  ))}
@@ -1,4 +1,5 @@
1
- // 通用图表数值工具:round-number 刻度生成,无第三方依赖(遵循 view/ 不引图表库的现状)。
1
+ // 通用图表数值工具:round-number 刻度生成与标签防撞布局,零依赖纯函数。
2
+ // 从 src/view/app/lib/chart.ts 迁来(view 侧从这里 re-export),报告组件与 view 共用同一份。
2
3
 
3
4
  function niceNumber(range: number, round: boolean): number {
4
5
  if (range <= 0) return 1;
@@ -34,11 +35,6 @@ export function niceTicks(min: number, max: number, count = 5): number[] {
34
35
  return ticks;
35
36
  }
36
37
 
37
- /** 六色分类色板之外的第 7+ 个系列一律回退到中性色,避免同色误导身份(见 dataviz 分类色板规则)。 */
38
- export function seriesColor(index: number): string {
39
- return index < 6 ? `var(--series-${index + 1})` : "var(--muted)";
40
- }
41
-
42
38
  export interface LabelInput {
43
39
  cx: number;
44
40
  cy: number;
@@ -1,21 +1,22 @@
1
- // 跨块配色一致(docs/reports.md 四条跨组件契约之二):
1
+ // 跨块配色一致(docs/feature/reports/architecture.md「静态网页」):
2
2
  // 系列/维度键 → 固定调色板下标,用稳定散列而不是「按出现顺序分配」。
3
3
  // 这样同一个 agent 在 scatter 的线、DeltaTable 的行、matrix 的列头永远同色,
4
4
  // 不需要 Provider、不需要手工配置,甚至不需要两个组件见过同一份数据。
5
- // 调色板的十六进制值以这里为准:SVG(scatter 的线和点)直接内联 hex,
6
- // 保证漏加载 styles.css 时图形仍然分得清系列;styles.css 里的 --nre-c0..c7
7
- // 是同一组值的拷贝(供 .nre-cN 文本类用),改色时两边一起改。
5
+ //
6
+ // 色板与 view 统一:六色 CVD 校验色板(src/view/styles.css --series-1..6),
7
+ // 顺序不要重排。深浅两套值都住在 styles.css 的 --nre-c0..c5(light-dark()),
8
+ // 「CSS 与 colors.ts 逐个对应」契约:NRE_PALETTE 是浅色主题那套值的拷贝,
9
+ // 渲染面优先挂类名(nre-cN 上文字色、nre-series-cN 上图形系列色)由 CSS 上色,
10
+ // 深色主题才能跟随;colorHexForKey 只留给「不经 CSS」的消费方。改色时两边一起改。
8
11
 
9
- /** 固定调色板;下标即 nre-cN 的 N,与 styles.css 的 --nre-cN 逐个对应。 */
12
+ /** 固定调色板(浅色主题值);下标即 nre-cN / nre-series-cN 的 N,与 styles.css 的 --nre-cN 逐个对应。 */
10
13
  export const NRE_PALETTE = [
11
- "#2563eb", // c0 蓝
12
- "#db2777", // c1 玫红
13
- "#059669", // c2 绿
14
- "#d97706", // c3
15
- "#7c3aed", // c4
16
- "#0891b2", // c5
17
- "#dc2626", // c6 红
18
- "#65a30d", // c7 橄榄
14
+ "#2a78d6", // c0 蓝(dark: #3987e5)
15
+ "#1baf7a", // c1 绿(dark: #199e70)
16
+ "#eda100", // c2 琥珀(dark: #c98500)
17
+ "#008300", // c3 深绿(dark: #008300)
18
+ "#e34948", // c4 红(dark: #e66767)
19
+ "#eb6834", // c5 橙(dark: #d95926)
19
20
  ] as const;
20
21
 
21
22
  export const NRE_PALETTE_SIZE = NRE_PALETTE.length;
@@ -31,12 +32,20 @@ export function colorIndexForKey(key: string): number {
31
32
  return hash % NRE_PALETTE_SIZE;
32
33
  }
33
34
 
34
- /** 键对应的稳定 class 名("nre-c3"),配 styles.css 的 .nre-cN 上色。 */
35
+ /** 键对应的稳定 class 名("nre-c3"),配 styles.css 的 .nre-cN 上文字色。 */
35
36
  export function colorClassForKey(key: string): string {
36
37
  return `nre-c${colorIndexForKey(key)}`;
37
38
  }
38
39
 
39
- /** 键对应的十六进制颜色,给 SVG 内联 stroke/fill 用(不依赖 CSS 加载)。 */
40
+ /**
41
+ * 键对应的系列 class 名("nre-series-c3"):挂在 SVG 图形元素(线/点/柱/系列名)上,
42
+ * styles.css 用它设置 --nre-series,fill/stroke 走 var —— 深色主题下图表随之切换。
43
+ */
44
+ export function seriesClassForKey(key: string): string {
45
+ return `nre-series-c${colorIndexForKey(key)}`;
46
+ }
47
+
48
+ /** 键对应的十六进制颜色(浅色主题值);渲染面优先类名,这里只留给不经 CSS 的消费方。 */
40
49
  export function colorHexForKey(key: string): string {
41
50
  return NRE_PALETTE[colorIndexForKey(key)];
42
51
  }