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
@@ -0,0 +1,113 @@
1
+ // AttemptList:实体列表的叶子层——每项一个 Attempt,固定展示判定、断言、error、Judge 评语
2
+ // (assertions 的 detail/evidence)与证据引用(locator)。它不预设只看失败,
3
+ // 报告作者过滤 items、用 .slice() 限量,total 让渲染面如实报告剩余数量,不静默截断。
4
+ // ExperimentList / EvalList 的下钻数组是同一个 AttemptListItem[],这里的渲染逻辑因此
5
+ // 也是它们展开区里"逐条 attempt"那一层的唯一实现(通过 AttemptRow 导出复用,不重写一遍)。
6
+
7
+ import type { ReactElement } from "react";
8
+ import type { AttemptListItem } from "../types.ts";
9
+ import type { AttemptLocator } from "../../results/locator.ts";
10
+ import { DEFAULT_REPORT_LOCALE, localeText, type ReportLocale } from "../locale.ts";
11
+ import { colorClassForKey } from "./colors.ts";
12
+ import { cx, formatDurationMs, formatUSD, verdictMark } from "./format.ts";
13
+
14
+ /** locator + 判定符的普通 <a>,AttemptList/EvalList/ExperimentList 共用。 */
15
+ export function AttemptLocatorBadge({
16
+ item,
17
+ attemptHref,
18
+ }: {
19
+ item: Pick<AttemptListItem, "locator" | "verdict">;
20
+ attemptHref: (locator: AttemptLocator) => string;
21
+ }): ReactElement {
22
+ return (
23
+ <a
24
+ className={cx("nre-locator", `nre-verdict-${item.verdict}`)}
25
+ href={attemptHref(item.locator)}
26
+ >
27
+ {item.locator}
28
+ <span className="nre-locator-mark">{verdictMark(item.verdict)}</span>
29
+ </a>
30
+ );
31
+ }
32
+
33
+ function AssertionRow({ assertion, locale }: { assertion: AttemptListItem["assertions"][number]; locale: ReportLocale }): ReactElement {
34
+ return (
35
+ <li className={cx("nre-assertion", `nre-assertion-${assertion.severity}`, !assertion.passed && "nre-assertion-failed")}>
36
+ <span className="nre-assertion-severity">{assertion.severity}</span>
37
+ <span className="nre-assertion-name">{assertion.name}</span>
38
+ <span className="nre-assertion-score">
39
+ {localeText(locale, "attemptList.score", { score: assertion.score })}
40
+ </span>
41
+ {(assertion.detail || assertion.evidence) && (
42
+ <details className="nre-assertion-more">
43
+ <summary>{localeText(locale, "attemptList.details")}</summary>
44
+ {assertion.detail && <p className="nre-assertion-detail">{assertion.detail}</p>}
45
+ {assertion.evidence && <blockquote className="nre-assertion-evidence">{assertion.evidence}</blockquote>}
46
+ </details>
47
+ )}
48
+ </li>
49
+ );
50
+ }
51
+
52
+ /** 一条 Attempt 的完整卡片:AttemptList 自己的一项,也是 EvalList 展开区里的一行。 */
53
+ export function AttemptRow({
54
+ item,
55
+ attemptHref,
56
+ locale = DEFAULT_REPORT_LOCALE,
57
+ }: {
58
+ item: AttemptListItem;
59
+ attemptHref: (locator: AttemptLocator) => string;
60
+ locale?: ReportLocale;
61
+ }): ReactElement {
62
+ return (
63
+ <li className={cx("nre-attempt", `nre-attempt-${item.verdict}`)}>
64
+ <div className="nre-attempt-head">
65
+ <AttemptLocatorBadge item={item} attemptHref={attemptHref} />
66
+ <span className="nre-attempt-eval">{item.evalId}</span>
67
+ <span className="nre-attempt-experiment">{item.experimentId}</span>
68
+ {/* agent 键:稳定散列上色,与其它块(MetricMatrix 列头、DeltaTable 行…)同键同色 */}
69
+ <span className={cx("nre-attempt-agent", "nre-key", colorClassForKey(item.agent))}>{item.agent}</span>
70
+ <span className="nre-attempt-duration">{formatDurationMs(item.durationMs)}</span>
71
+ {item.costUSD !== undefined && <span className="nre-attempt-cost">{formatUSD(item.costUSD)}</span>}
72
+ </div>
73
+ {item.error && <p className="nre-attempt-error">{item.error}</p>}
74
+ {item.assertions.length > 0 && (
75
+ <ul className="nre-assertions">
76
+ {item.assertions.map((a, i) => (
77
+ <AssertionRow key={i} assertion={a} locale={locale} />
78
+ ))}
79
+ </ul>
80
+ )}
81
+ </li>
82
+ );
83
+ }
84
+
85
+ export function AttemptList({
86
+ items,
87
+ total,
88
+ attemptHref,
89
+ className,
90
+ locale = DEFAULT_REPORT_LOCALE,
91
+ }: {
92
+ items: AttemptListItem[];
93
+ /** items 被 slice 时的原始数量;如实显示还剩多少条没展示。 */
94
+ total?: number;
95
+ attemptHref: (locator: AttemptLocator) => string;
96
+ className?: string;
97
+ locale?: ReportLocale;
98
+ }): ReactElement {
99
+ const remaining = (total ?? items.length) - items.length;
100
+ return (
101
+ <section className={cx("nre", "nre-attempt-list", className)}>
102
+ {items.length === 0 && <p className="nre-attempt-list-empty">{localeText(locale, "attemptList.empty")}</p>}
103
+ <ul className="nre-attempts">
104
+ {items.map((item) => (
105
+ <AttemptRow key={item.locator} item={item} attemptHref={attemptHref} locale={locale} />
106
+ ))}
107
+ </ul>
108
+ {remaining > 0 && (
109
+ <p className="nre-truncated">{localeText(locale, "attemptList.truncated", { n: remaining })}</p>
110
+ )}
111
+ </section>
112
+ );
113
+ }
@@ -4,9 +4,10 @@
4
4
 
5
5
  import type { ReactElement } from "react";
6
6
  import type { DeltaData, MetricColumn } from "../types.ts";
7
+ import { DEFAULT_REPORT_LOCALE, localeText, resolveMetricLabel, type ReportLocale } from "../locale.ts";
7
8
  import { MetricCellView } from "./cell.tsx";
8
9
  import { colorClassForKey } from "./colors.ts";
9
- import { MISSING_TEXT, cx } from "./format.ts";
10
+ import { cx } from "./format.ts";
10
11
 
11
12
  /** Δ 的语义配色 class:好/坏由 better 方向判定,不看正负号本身。 */
12
13
  function deltaToneClass(delta: number | null, better: MetricColumn["better"]): string {
@@ -20,20 +21,22 @@ function deltaToneClass(delta: number | null, better: MetricColumn["better"]): s
20
21
  export function DeltaTable({
21
22
  data,
22
23
  className,
24
+ locale = DEFAULT_REPORT_LOCALE,
23
25
  }: {
24
26
  data: DeltaData;
25
27
  className?: string;
28
+ locale?: ReportLocale;
26
29
  }): ReactElement {
27
30
  return (
28
31
  <table className={cx("nre", "nre-delta-table", className)}>
29
32
  <thead>
30
33
  <tr>
31
34
  <th scope="col" className="nre-dimension">
32
- pair (A → B)
35
+ {localeText(locale, "delta.pairHeader")}
33
36
  </th>
34
37
  {data.columns.map((col) => (
35
38
  <th scope="col" key={col.key} className="nre-metric-col">
36
- {col.label}
39
+ {resolveMetricLabel(col.label, locale, col.key)}
37
40
  {col.unit && <span className="nre-unit">({col.unit})</span>}
38
41
  </th>
39
42
  ))}
@@ -57,16 +60,16 @@ export function DeltaTable({
57
60
  {/* A/B 走统一的 MetricCellView:缺数据文案与覆盖率角标同一套 */}
58
61
  <span className="nre-delta-side nre-delta-a">
59
62
  <span className="nre-delta-tag">A</span>
60
- <MetricCellView cell={cell.a} />
63
+ <MetricCellView cell={cell.a} locale={locale} />
61
64
  </span>
62
65
  <span className="nre-delta-side nre-delta-b">
63
66
  <span className="nre-delta-tag">B</span>
64
- <MetricCellView cell={cell.b} />
67
+ <MetricCellView cell={cell.b} locale={locale} />
65
68
  </span>
66
69
  <span className={cx("nre-delta-side", "nre-delta-d", deltaToneClass(cell.delta, col.better))}>
67
70
  <span className="nre-delta-tag">Δ</span>
68
71
  {/* 任一侧 null → delta null:显示缺,不硬算 */}
69
- {cell.delta === null ? <span className="nre-missing">{MISSING_TEXT}</span> : cell.display}
72
+ {cell.delta === null ? <span className="nre-missing">{localeText(locale, "cell.missing")}</span> : cell.display}
70
73
  </span>
71
74
  </td>
72
75
  );
Binary file
@@ -0,0 +1,220 @@
1
+ // ExperimentList:实体列表的第一级。web 面是一行一个 experiment 的固定列比较表;
2
+ // 每行用原生 <details> 展开到 Eval 与 Attempt locator。数据仍完全来自
3
+ // ExperimentList.data(),不恢复旧 ExperimentTable 的混合实体计算层。
4
+
5
+ import type { ReactElement } from "react";
6
+ import type { AttemptLocator } from "../../results/locator.ts";
7
+ import type { AttemptListItem, ExperimentListEvalRow, ExperimentListItem } from "../types.ts";
8
+ import { attemptItemReason } from "../format.ts";
9
+ import { DEFAULT_REPORT_LOCALE, localeText, type ReportLocale } from "../locale.ts";
10
+ import { AttemptLocatorBadge } from "./AttemptList.tsx";
11
+ import { MetricCellView } from "./cell.tsx";
12
+ import { colorClassForKey } from "./colors.ts";
13
+ import { cx, formatDurationMs, formatUSD, verdictMark } from "./format.ts";
14
+
15
+ const verdictOrder = ["passed", "failed", "errored", "skipped"] as const;
16
+
17
+ function passRateTone(value: number | null): string | undefined {
18
+ if (value === null) return undefined;
19
+ if (value >= 0.8) return "nre-good";
20
+ if (value >= 0.5) return "nre-warn";
21
+ return "nre-bad";
22
+ }
23
+
24
+ function formatDate(value: string, locale: ReportLocale): string {
25
+ const date = new Date(value);
26
+ if (Number.isNaN(date.valueOf())) return value;
27
+ return new Intl.DateTimeFormat(locale === "zh-CN" ? "zh-CN" : "en", {
28
+ month: "short",
29
+ day: "numeric",
30
+ hour: "2-digit",
31
+ minute: "2-digit",
32
+ }).format(date);
33
+ }
34
+
35
+ function VerdictSummary({ item, locale }: { item: ExperimentListItem; locale: ReportLocale }): ReactElement {
36
+ const parts = verdictOrder
37
+ .filter((verdict) => item.verdicts[verdict] > 0)
38
+ .map((verdict) => `${item.verdicts[verdict]} ${localeText(locale, `verdict.${verdict}`)}`);
39
+ return <span className="nre-experiment-pill">{parts.join(" / ") || "—"}</span>;
40
+ }
41
+
42
+ function ExperimentAttemptRow({
43
+ attempt,
44
+ last,
45
+ attemptHref,
46
+ }: {
47
+ attempt: AttemptListItem;
48
+ last: boolean;
49
+ attemptHref: (locator: AttemptLocator) => string;
50
+ }): ReactElement {
51
+ const reason = attemptItemReason(attempt);
52
+ return (
53
+ <li className={cx("nre-experiment-attempt-row", `nre-eval-${attempt.verdict}`)}>
54
+ <span className="nre-attempt-branch" aria-hidden="true">{last ? "└─" : "├─"}</span>
55
+ <span className="nre-eval-attempt-badges">
56
+ <AttemptLocatorBadge item={attempt} attemptHref={attemptHref} />
57
+ </span>
58
+ <span className="nre-eval-attempt-metrics">
59
+ {formatDurationMs(attempt.durationMs)}
60
+ {attempt.costUSD !== undefined && <> · {formatUSD(attempt.costUSD)}</>}
61
+ </span>
62
+ <span className="nre-eval-reason">{reason ?? "—"}</span>
63
+ </li>
64
+ );
65
+ }
66
+
67
+ function EvalAttempts({
68
+ row,
69
+ attemptHref,
70
+ locale,
71
+ }: {
72
+ row: ExperimentListEvalRow;
73
+ attemptHref: (locator: AttemptLocator) => string;
74
+ locale: ReportLocale;
75
+ }): ReactElement {
76
+ return (
77
+ <li className="nre-experiment-eval">
78
+ <div className={cx("nre-experiment-eval-header", `nre-eval-${row.verdict}`)}>
79
+ <span className={cx("nre-eval-verdict", `nre-verdict-${row.verdict}`)}>{verdictMark(row.verdict)}</span>
80
+ <span className="nre-eval-id">{row.evalId}</span>
81
+ <span className="nre-eval-attempt-count">{localeText(locale, "overview.attemptsCount", { n: row.attempts.length })}</span>
82
+ <span className="nre-eval-summary">
83
+ {row.verdict === "passed"
84
+ ? [formatDurationMs(row.duration.value ?? 0), row.cost.value === null ? undefined : formatUSD(row.cost.value)]
85
+ .filter((value): value is string => value !== undefined)
86
+ .join(" · ")
87
+ : row.reason}
88
+ </span>
89
+ </div>
90
+ <ul className="nre-experiment-attempts">
91
+ {row.attempts.map((attempt, index) => (
92
+ <ExperimentAttemptRow
93
+ key={attempt.locator}
94
+ attempt={attempt}
95
+ last={index === row.attempts.length - 1}
96
+ attemptHref={attemptHref}
97
+ />
98
+ ))}
99
+ </ul>
100
+ </li>
101
+ );
102
+ }
103
+
104
+ function Flags({ flags, locale }: { flags: Record<string, unknown> | undefined; locale: ReportLocale }): ReactElement | null {
105
+ if (!flags || Object.keys(flags).length === 0) return null;
106
+ return (
107
+ <div className="nre-experiment-flags">
108
+ <span>{localeText(locale, "experimentList.flags")}</span>
109
+ {Object.entries(flags).map(([key, value]) => (
110
+ <b key={key}>{key}={String(value)}</b>
111
+ ))}
112
+ </div>
113
+ );
114
+ }
115
+
116
+ function ExperimentRow({
117
+ item,
118
+ attemptHref,
119
+ locale,
120
+ }: {
121
+ item: ExperimentListItem;
122
+ attemptHref: (locator: AttemptLocator) => string;
123
+ locale: ReportLocale;
124
+ }): ReactElement {
125
+ return (
126
+ <details className="nre-experiment-entry">
127
+ <summary className="nre-experiment-summary">
128
+ <span className="nre-experiment-name" data-sort-value={item.experimentId}>
129
+ <b className={cx("nre-experiment-id", "nre-key", colorClassForKey(item.experimentId))}>{item.experimentId}</b>
130
+ <small>
131
+ {localeText(locale, "overview.evalsCount", { n: item.evals })}
132
+ {item.attempts > item.evals ? ` · ${localeText(locale, "overview.attemptsCount", { n: item.attempts })}` : ""}
133
+ {` · ${formatDate(item.lastRunAt, locale)}`}
134
+ </small>
135
+ </span>
136
+ <span data-sort-value={item.model ?? ""}>{item.model ?? localeText(locale, "experimentList.defaultModel")}</span>
137
+ <span data-sort-value={item.agent}>{item.agent}</span>
138
+ <span className="nre-num" data-sort-value={item.duration.value ?? ""}>
139
+ <MetricCellView cell={item.duration} locale={locale} />
140
+ </span>
141
+ <span className={cx("nre-num", passRateTone(item.passRate.value))} data-sort-value={item.passRate.value ?? ""}>
142
+ <MetricCellView cell={item.passRate} locale={locale} />
143
+ </span>
144
+ <span className="nre-num" data-sort-value={item.tokens.value ?? ""}>
145
+ <MetricCellView cell={item.tokens} locale={locale} />
146
+ </span>
147
+ <span className="nre-num" data-sort-value={item.cost.value ?? ""}>
148
+ <MetricCellView cell={item.cost} locale={locale} />
149
+ </span>
150
+ <span data-sort-value={item.verdicts.passed}><VerdictSummary item={item} locale={locale} /></span>
151
+ </summary>
152
+ <div className="nre-experiment-detail">
153
+ <Flags flags={item.flags} locale={locale} />
154
+ <ul className="nre-experiment-evals">
155
+ {item.evalRows.map((row) => (
156
+ <EvalAttempts key={row.evalId} row={row} attemptHref={attemptHref} locale={locale} />
157
+ ))}
158
+ </ul>
159
+ </div>
160
+ </details>
161
+ );
162
+ }
163
+
164
+ export function ExperimentList({
165
+ items,
166
+ attemptHref,
167
+ filter = false,
168
+ className,
169
+ locale = DEFAULT_REPORT_LOCALE,
170
+ }: {
171
+ items: ExperimentListItem[];
172
+ attemptHref: (locator: AttemptLocator) => string;
173
+ filter?: boolean;
174
+ className?: string;
175
+ locale?: ReportLocale;
176
+ }): ReactElement {
177
+ const labels = [
178
+ localeText(locale, "experimentList.experiment"),
179
+ localeText(locale, "table.model"),
180
+ localeText(locale, "table.agent"),
181
+ localeText(locale, "experimentList.avgDuration"),
182
+ localeText(locale, "experimentList.passRate"),
183
+ localeText(locale, "experimentList.tokens"),
184
+ localeText(locale, "experimentList.estimatedCost"),
185
+ localeText(locale, "experimentList.result"),
186
+ ];
187
+ const board = (
188
+ <div className="nre-experiment-table">
189
+ <div className="nre-experiment-head">
190
+ {labels.map((label, index) => (
191
+ <button
192
+ type="button"
193
+ data-nre-experiment-sort={index}
194
+ className={index === 4 ? "nre-sort-desc" : undefined}
195
+ key={label}
196
+ >
197
+ {label}
198
+ </button>
199
+ ))}
200
+ </div>
201
+ {items.length === 0 && <p className="nre-experiment-list-empty">{localeText(locale, "attemptList.empty")}</p>}
202
+ {items.map((item) => (
203
+ <ExperimentRow key={item.experimentId} item={item} attemptHref={attemptHref} locale={locale} />
204
+ ))}
205
+ </div>
206
+ );
207
+ return (
208
+ <div className={cx("nre", "nre-experiment-list", filter && "nre-experiment-table-wrap", className)}>
209
+ {filter && (
210
+ <input
211
+ className="nre-filter"
212
+ data-nre-experiment-filter=""
213
+ type="search"
214
+ placeholder={localeText(locale, "experimentList.filterPlaceholder")}
215
+ />
216
+ )}
217
+ {board}
218
+ </div>
219
+ );
220
+ }
@@ -0,0 +1,66 @@
1
+ // GroupSummary:一组 experiment(如自定义报告里同一 <Section> 内的全部 experiment)的
2
+ // 紧凑摘要块——恢复旧 GroupSelector 卡片的信息密度(通过率、experiment/eval 数、failed、
3
+ // errored、总成本、最后运行时间),但只渲染 GroupSummaryData 算好的 MetricCell / 计数,
4
+ // 不现场重算比例(docs/feature/reports/library.md「GroupSummary」)。errored 为 0 时省略该片段(旧卡片同一姿态),
5
+ // 但这只是渲染取舍——数据侧 verdicts.errored 字段本身永不省略。
6
+
7
+ import type { ReactElement } from "react";
8
+ import type { GroupSummaryData } from "../types.ts";
9
+ import { DEFAULT_REPORT_LOCALE, localeText, type ReportLocale } from "../locale.ts";
10
+ import { MetricCellView } from "./cell.tsx";
11
+ import { cx, formatUSD } from "./format.ts";
12
+
13
+ export function GroupSummary({
14
+ data,
15
+ className,
16
+ locale = DEFAULT_REPORT_LOCALE,
17
+ }: {
18
+ data: GroupSummaryData;
19
+ className?: string;
20
+ locale?: ReportLocale;
21
+ }): ReactElement {
22
+ const missing = <span className="nre-missing">{localeText(locale, "cell.missing")}</span>;
23
+ return (
24
+ <div className={cx("nre", "nre-group-summary", className)}>
25
+ <dl className="nre-group-kpis">
26
+ <div className="nre-group-kpi nre-group-kpi-rate">
27
+ <dt>{localeText(locale, "overview.passRate")}</dt>
28
+ <dd>
29
+ <MetricCellView cell={data.passRate} locale={locale} />
30
+ </dd>
31
+ </div>
32
+ <div className="nre-group-kpi">
33
+ <dt>{localeText(locale, "groupSummary.experiments")}</dt>
34
+ <dd>{data.experiments}</dd>
35
+ </div>
36
+ <div className="nre-group-kpi">
37
+ <dt>{localeText(locale, "overview.evals")}</dt>
38
+ <dd>{data.evals}</dd>
39
+ </div>
40
+ <div className="nre-group-kpi">
41
+ <dt>{localeText(locale, "overview.attempts")}</dt>
42
+ <dd>{data.attempts}</dd>
43
+ </div>
44
+ <div className="nre-group-kpi">
45
+ <dt>{localeText(locale, "verdict.failed")}</dt>
46
+ <dd className="nre-verdict-failed">{data.verdicts.failed}</dd>
47
+ </div>
48
+ {/* errored 为 0 省略这一片段(旧 GroupSelector 卡片同一姿态);数据字段本身不受影响 */}
49
+ {data.verdicts.errored > 0 && (
50
+ <div className="nre-group-kpi">
51
+ <dt>{localeText(locale, "verdict.errored")}</dt>
52
+ <dd className="nre-verdict-errored">{data.verdicts.errored}</dd>
53
+ </div>
54
+ )}
55
+ <div className="nre-group-kpi">
56
+ <dt>{localeText(locale, "overview.totalCost")}</dt>
57
+ {/* totalCostUSD 全缺 = null:显示缺数据,不编 $0 */}
58
+ <dd>{data.totalCostUSD === null ? missing : formatUSD(data.totalCostUSD)}</dd>
59
+ </div>
60
+ </dl>
61
+ {data.lastRunAt && (
62
+ <p className="nre-group-summary-time">{localeText(locale, "latestRun", { run: data.lastRunAt })}</p>
63
+ )}
64
+ </div>
65
+ );
66
+ }
@@ -1,11 +1,13 @@
1
1
  // MetricBars:分组条形——同一份矩阵数据的另一种摆法(MetricBars.data = MetricMatrix.data)。
2
2
  // 组维度一组条、系列维度一根条、条长是指标值;竖向分组柱,柱顶标数值,系列颜色与
3
- // 其它组件的稳定配色一致,图例自动生成。组内按值排序,方向随 better;
4
- // 缺数据的系列不画柱、不编 0(与 text 面的 — 同口径)。
3
+ // 其它组件的稳定配色一致(类名 nre-series-cN 由 CSS 上色,深色主题跟随),图例自动生成。
4
+ // 组内按值排序,方向随 better;缺数据的系列不画柱、不编 0(与 text 面的 — 同口径)。
5
5
 
6
6
  import type { ReactElement } from "react";
7
- import type { AttemptRef, MatrixData, MetricCell } from "../types.ts";
8
- import { colorHexForKey, colorClassForKey } from "./colors.ts";
7
+ import type { MatrixData, MetricCell } from "../types.ts";
8
+ import type { AttemptLocator } from "../../results/locator.ts";
9
+ import { DEFAULT_REPORT_LOCALE, resolveMetricLabel, type ReportLocale } from "../locale.ts";
10
+ import { colorClassForKey, seriesClassForKey } from "./colors.ts";
9
11
  import { cx } from "./format.ts";
10
12
 
11
13
  const WIDTH = 640;
@@ -16,10 +18,12 @@ export function MetricBars({
16
18
  data,
17
19
  attemptHref,
18
20
  className,
21
+ locale = DEFAULT_REPORT_LOCALE,
19
22
  }: {
20
23
  data: MatrixData;
21
- attemptHref?: (ref: AttemptRef) => string;
24
+ attemptHref?: (locator: AttemptLocator) => string;
22
25
  className?: string;
26
+ locale?: ReportLocale;
23
27
  }): ReactElement {
24
28
  // 稀疏 cells → 首次出现顺序的组/系列键 + 查找表
25
29
  const groupKeys: string[] = [];
@@ -31,6 +35,7 @@ export function MetricBars({
31
35
  byPosition.set(JSON.stringify([entry.row, entry.column]), entry.cell);
32
36
  }
33
37
 
38
+ const metricLabel = resolveMetricLabel(data.metric.label, locale, data.metric.key);
34
39
  const better = data.metric.better ?? "higher";
35
40
  const values = data.cells.map((c) => c.cell.value).filter((v): v is number => v !== null);
36
41
  const maxValue = values.length > 0 ? Math.max(...values) : 0;
@@ -47,7 +52,7 @@ export function MetricBars({
47
52
  className="nre-bars-svg"
48
53
  viewBox={`0 0 ${WIDTH} ${HEIGHT}`}
49
54
  role="img"
50
- aria-label={`${data.metric.label} by ${data.rows} × ${data.columns}`}
55
+ aria-label={`${metricLabel} by ${data.rows} × ${data.columns}`}
51
56
  >
52
57
  {groupKeys.map((group, gi) => {
53
58
  const x0 = PLOT.left + gi * groupWidth;
@@ -68,34 +73,40 @@ export function MetricBars({
68
73
  const y = PLOT.bottom - h;
69
74
  const title = `${group} · ${series}: ${cell.display}(${cell.samples}/${cell.total})`;
70
75
  const rect = (
71
- <rect className="nre-bar" x={x + 2} y={y} width={barWidth - 4} height={h} fill={colorHexForKey(series)}>
76
+ <rect
77
+ className={cx("nre-bar", seriesClassForKey(series))}
78
+ x={x + 2}
79
+ y={y}
80
+ width={barWidth - 4}
81
+ height={h}
82
+ >
72
83
  <title>{title}</title>
73
84
  </rect>
74
85
  );
75
- const ref = cell.refs[0];
86
+ const locator = cell.refs[0];
76
87
  return (
77
88
  <g key={series}>
78
- {attemptHref && ref ? <a href={attemptHref(ref)}>{rect}</a> : rect}
89
+ {attemptHref && locator ? <a href={attemptHref(locator)}>{rect}</a> : rect}
79
90
  {/* 柱顶标数值;覆盖不全时把 samples/total 一并标出,不藏 */}
80
- <text className="nre-bar-value" x={x + barWidth / 2} y={y - 4} textAnchor="middle" fontSize={10} fill="#525252">
91
+ <text className="nre-bar-value" x={x + barWidth / 2} y={y - 4} textAnchor="middle">
81
92
  {cell.samples < cell.total ? `${cell.display} ${cell.samples}/${cell.total}` : cell.display}
82
93
  </text>
83
94
  </g>
84
95
  );
85
96
  })}
86
- <text className="nre-bars-group-label" x={x0 + groupWidth / 2} y={HEIGHT - 20} textAnchor="middle" fontSize={12} fill="#525252">
97
+ <text className="nre-bars-group-label" x={x0 + groupWidth / 2} y={HEIGHT - 20} textAnchor="middle">
87
98
  {group}
88
99
  </text>
89
100
  </g>
90
101
  );
91
102
  })}
92
103
  {/* 基线 */}
93
- <line x1={PLOT.left} y1={PLOT.bottom} x2={PLOT.right} y2={PLOT.bottom} stroke="#d4d4d4" />
104
+ <line className="nre-bars-baseline" x1={PLOT.left} y1={PLOT.bottom} x2={PLOT.right} y2={PLOT.bottom} />
94
105
  </svg>
95
106
  {/* 图例:系列 → 稳定配色,跨块同键同色 */}
96
107
  <figcaption className="nre-bars-legend">
97
108
  <span className="nre-bars-metric">
98
- {data.metric.label}
109
+ {metricLabel}
99
110
  {data.metric.unit && <span className="nre-unit">({data.metric.unit})</span>}
100
111
  </span>
101
112
  {seriesKeys.map((series) => (