niceeval 0.5.3 → 0.6.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 (344) hide show
  1. package/INDEX.md +55 -0
  2. package/dist/agents/types.d.ts +317 -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/execution-tree.d.ts +103 -0
  9. package/dist/o11y/otlp/parse.d.ts +3 -0
  10. package/dist/o11y/otlp/receiver.d.ts +21 -0
  11. package/dist/o11y/otlp/select.d.ts +22 -0
  12. package/dist/o11y/otlp/turn-otel.d.ts +43 -0
  13. package/dist/o11y/types.d.ts +190 -0
  14. package/dist/report/aggregate.d.ts +57 -0
  15. package/dist/report/aggregate.js +212 -0
  16. package/dist/report/built-ins/cost-pass-rate-comparison.d.ts +1 -0
  17. package/dist/report/built-ins/cost-pass-rate-comparison.js +17 -0
  18. package/dist/report/built-ins/index.d.ts +1 -0
  19. package/dist/report/built-ins/index.js +2 -0
  20. package/dist/report/components.d.ts +178 -0
  21. package/dist/report/components.js +132 -0
  22. package/dist/report/compute.d.ts +106 -0
  23. package/dist/report/compute.js +618 -0
  24. package/dist/report/flag.d.ts +6 -0
  25. package/dist/report/flag.js +8 -0
  26. package/dist/report/format.d.ts +32 -0
  27. package/dist/report/format.js +126 -0
  28. package/dist/report/index.d.ts +19 -0
  29. package/dist/report/index.js +30 -0
  30. package/dist/report/load.d.ts +12 -0
  31. package/dist/report/load.js +56 -0
  32. package/dist/report/locale.d.ts +83 -0
  33. package/dist/report/locale.js +160 -0
  34. package/dist/report/metrics.d.ts +21 -0
  35. package/dist/report/metrics.js +115 -0
  36. package/dist/report/primitives.d.ts +64 -0
  37. package/dist/report/primitives.js +113 -0
  38. package/dist/report/react/AttemptList.d.ts +23 -0
  39. package/dist/report/react/AttemptList.js +20 -0
  40. package/dist/report/react/DeltaTable.d.ts +8 -0
  41. package/dist/report/react/DeltaTable.js +24 -0
  42. package/dist/report/react/EvalList.d.ts +10 -0
  43. package/dist/report/react/EvalList.js +0 -0
  44. package/dist/report/react/ExperimentList.d.ts +10 -0
  45. package/dist/report/react/ExperimentList.js +15 -0
  46. package/dist/report/react/GroupSummary.d.ts +8 -0
  47. package/dist/report/react/GroupSummary.js +8 -0
  48. package/dist/report/react/MetricBars.d.ts +10 -0
  49. package/dist/report/react/MetricBars.js +48 -0
  50. package/dist/report/react/MetricLine.d.ts +9 -0
  51. package/dist/report/react/MetricLine.js +68 -0
  52. package/dist/report/react/MetricMatrix.d.ts +10 -0
  53. package/dist/report/react/MetricMatrix.js +23 -0
  54. package/dist/report/react/MetricScatter.d.ts +9 -0
  55. package/dist/report/react/MetricScatter.js +100 -0
  56. package/dist/report/react/MetricTable.d.ts +11 -0
  57. package/dist/report/react/MetricTable.js +26 -0
  58. package/dist/report/react/RunOverview.d.ts +8 -0
  59. package/dist/report/react/RunOverview.js +12 -0
  60. package/dist/report/react/Scoreboard.d.ts +8 -0
  61. package/dist/report/react/Scoreboard.js +26 -0
  62. package/dist/report/react/cell.d.ts +9 -0
  63. package/dist/report/react/cell.js +9 -0
  64. package/dist/report/react/chart-math.d.ts +14 -0
  65. package/dist/report/react/chart-math.js +68 -0
  66. package/dist/report/react/colors.d.ts +14 -0
  67. package/dist/report/react/colors.js +45 -0
  68. package/dist/report/react/fixtures.d.ts +19 -0
  69. package/dist/report/react/fixtures.js +393 -0
  70. package/dist/report/react/format.d.ts +3 -0
  71. package/dist/report/react/format.js +7 -0
  72. package/dist/report/react/index.d.ts +6 -0
  73. package/dist/report/react/index.js +14 -0
  74. package/dist/report/report.d.ts +24 -0
  75. package/dist/report/report.js +49 -0
  76. package/dist/report/text/faces.d.ts +29 -0
  77. package/dist/report/text/faces.js +503 -0
  78. package/dist/report/text/layout.d.ts +75 -0
  79. package/dist/report/text/layout.js +173 -0
  80. package/dist/report/text/plot.d.ts +37 -0
  81. package/dist/report/text/plot.js +81 -0
  82. package/dist/report/text/table.d.ts +8 -0
  83. package/dist/report/text/table.js +86 -0
  84. package/dist/report/tree.d.ts +99 -0
  85. package/dist/report/tree.js +228 -0
  86. package/dist/report/types.d.ts +420 -0
  87. package/dist/report/types.js +4 -0
  88. package/dist/report/web.d.ts +15 -0
  89. package/dist/report/web.js +37 -0
  90. package/dist/results/annotated-source.d.ts +61 -0
  91. package/dist/results/attempt-evidence.d.ts +69 -0
  92. package/dist/results/attempt-source.d.ts +15 -0
  93. package/dist/results/locator.d.ts +75 -0
  94. package/dist/results/locator.js +0 -0
  95. package/dist/results/select.d.ts +45 -0
  96. package/dist/results/select.js +230 -0
  97. package/dist/results/source-hash.d.ts +9 -0
  98. package/dist/results/types.d.ts +204 -0
  99. package/dist/runner/eval-source.d.ts +22 -0
  100. package/dist/runner/fingerprint.d.ts +23 -0
  101. package/dist/runner/types.d.ts +332 -0
  102. package/dist/sandbox/docker-stream.d.ts +24 -0
  103. package/dist/sandbox/docker.d.ts +84 -0
  104. package/dist/sandbox/e2b.d.ts +31 -0
  105. package/dist/sandbox/errors.d.ts +4 -0
  106. package/dist/sandbox/local-files.d.ts +6 -0
  107. package/dist/sandbox/paths.d.ts +4 -0
  108. package/dist/sandbox/registry.d.ts +13 -0
  109. package/dist/sandbox/resolve.d.ts +40 -0
  110. package/dist/sandbox/retry.d.ts +10 -0
  111. package/dist/sandbox/shell.d.ts +11 -0
  112. package/dist/sandbox/source-files.d.ts +13 -0
  113. package/dist/sandbox/types.d.ts +203 -0
  114. package/dist/sandbox/vercel.d.ts +37 -0
  115. package/dist/scoring/types.d.ts +70 -0
  116. package/dist/shared/aggregate.d.ts +29 -0
  117. package/dist/shared/aggregate.js +55 -0
  118. package/dist/shared/types.d.ts +27 -0
  119. package/dist/shared/verdict.d.ts +27 -0
  120. package/dist/shared/verdict.js +34 -0
  121. package/dist/tty-line.d.ts +6 -0
  122. package/dist/types.d.ts +7 -0
  123. package/dist/util.d.ts +22 -0
  124. package/docs-site/zh/concepts/adapter.mdx +9 -4
  125. package/docs-site/zh/concepts/assert.mdx +8 -8
  126. package/docs-site/zh/concepts/evals.mdx +5 -5
  127. package/docs-site/zh/concepts/experiment.mdx +3 -1
  128. package/docs-site/zh/concepts/judge.mdx +9 -9
  129. package/docs-site/zh/concepts/overview.mdx +2 -2
  130. package/docs-site/zh/concepts/tier.mdx +6 -6
  131. package/docs-site/zh/example/claude-code-codex-plugin.mdx +9 -5
  132. package/docs-site/zh/example/claude-code-codex-skill.mdx +12 -7
  133. package/docs-site/zh/guides/agent-feedback-loop.mdx +235 -78
  134. package/docs-site/zh/guides/connect-otel.mdx +3 -3
  135. package/docs-site/zh/guides/connect-your-agent.mdx +2 -2
  136. package/docs-site/zh/guides/custom-reports.mdx +196 -36
  137. package/docs-site/zh/guides/experiments.mdx +7 -0
  138. package/docs-site/zh/guides/fixtures.mdx +1 -1
  139. package/docs-site/zh/guides/official-adapters.mdx +31 -9
  140. package/docs-site/zh/guides/publish-report.mdx +4 -4
  141. package/docs-site/zh/guides/report-components.mdx +194 -55
  142. package/docs-site/zh/guides/reporters.mdx +2 -2
  143. package/docs-site/zh/guides/results-data.mdx +35 -34
  144. package/docs-site/zh/guides/runner.mdx +2 -2
  145. package/docs-site/zh/guides/sandbox-agent.mdx +10 -5
  146. package/docs-site/zh/guides/sandbox-providers.mdx +102 -0
  147. package/docs-site/zh/guides/viewing-results.mdx +199 -79
  148. package/docs-site/zh/guides/write-experiment.mdx +23 -1
  149. package/docs-site/zh/guides/write-send.mdx +2 -2
  150. package/docs-site/zh/index.mdx +4 -3
  151. package/docs-site/zh/introduction.mdx +2 -1
  152. package/docs-site/zh/quickstart.mdx +5 -3
  153. package/docs-site/zh/reference/builtin-agents.mdx +38 -13
  154. package/docs-site/zh/reference/capabilities.mdx +1 -1
  155. package/docs-site/zh/reference/cli.mdx +36 -13
  156. package/docs-site/zh/reference/define-agent.mdx +24 -12
  157. package/docs-site/zh/reference/define-config.mdx +1 -1
  158. package/docs-site/zh/reference/define-eval.mdx +3 -3
  159. package/docs-site/zh/reference/events.mdx +7 -1
  160. package/docs-site/zh/reference/expect.mdx +3 -3
  161. package/package.json +10 -6
  162. package/src/agents/bub.ts +134 -43
  163. package/src/agents/claude-code.test.ts +152 -0
  164. package/src/agents/claude-code.ts +127 -13
  165. package/src/agents/codex.test.ts +192 -0
  166. package/src/agents/codex.ts +151 -12
  167. package/src/agents/index.ts +11 -3
  168. package/src/agents/manifest.ts +41 -0
  169. package/src/agents/plugin-config.test.ts +94 -0
  170. package/src/agents/shared.ts +33 -2
  171. package/src/agents/skills.test.ts +214 -0
  172. package/src/agents/skills.ts +304 -0
  173. package/src/agents/streaming.ts +5 -5
  174. package/src/agents/types.ts +62 -2
  175. package/src/cli.ts +74 -47
  176. package/src/context/context.test.ts +20 -2
  177. package/src/context/context.ts +3 -0
  178. package/src/context/control-flow.ts +1 -1
  179. package/src/context/session.test.ts +92 -1
  180. package/src/context/session.ts +26 -1
  181. package/src/context/types.ts +4 -4
  182. package/src/define.test.ts +82 -0
  183. package/src/define.ts +72 -11
  184. package/src/expect/index.ts +5 -5
  185. package/src/i18n/en.ts +36 -15
  186. package/src/i18n/zh-CN.ts +39 -21
  187. package/src/index.ts +1 -1
  188. package/src/o11y/execution-tree.test.ts +452 -0
  189. package/src/o11y/execution-tree.ts +367 -0
  190. package/src/o11y/otlp/receiver.ts +1 -1
  191. package/src/o11y/otlp/select.ts +7 -3
  192. package/src/o11y/parsers/claude-code.test.ts +175 -0
  193. package/src/o11y/parsers/claude-code.ts +32 -0
  194. package/src/o11y/types.ts +6 -1
  195. package/src/report/aggregate.ts +26 -20
  196. package/src/report/built-in-user-parity.test.tsx +643 -0
  197. package/src/report/built-ins/cost-pass-rate-comparison.tsx +23 -0
  198. package/src/report/built-ins/index.ts +2 -0
  199. package/src/report/components.tsx +223 -50
  200. package/src/report/compute.ts +358 -53
  201. package/src/report/dual-render.test.tsx +1125 -0
  202. package/src/report/flag.ts +1 -1
  203. package/src/report/format.ts +50 -2
  204. package/src/report/index.ts +48 -14
  205. package/src/report/load.ts +1 -1
  206. package/src/report/locale.ts +206 -0
  207. package/src/report/metrics.ts +39 -15
  208. package/src/report/primitives.tsx +100 -2
  209. package/src/report/react/AttemptList.tsx +115 -0
  210. package/src/report/react/DeltaTable.tsx +9 -6
  211. package/src/report/react/EvalList.tsx +0 -0
  212. package/src/report/react/ExperimentList.tsx +108 -0
  213. package/src/report/react/GroupSummary.tsx +66 -0
  214. package/src/report/react/MetricBars.tsx +24 -13
  215. package/src/report/react/MetricLine.tsx +21 -21
  216. package/src/report/react/MetricMatrix.tsx +8 -4
  217. package/src/report/react/MetricScatter.tsx +136 -96
  218. package/src/report/react/MetricTable.tsx +115 -15
  219. package/src/report/react/RunOverview.tsx +48 -26
  220. package/src/report/react/Scoreboard.tsx +15 -7
  221. package/src/report/react/cell.tsx +18 -9
  222. package/src/{view/app/lib/chart.ts → report/react/chart-math.ts} +2 -6
  223. package/src/report/react/colors.ts +24 -15
  224. package/src/report/react/enhance.js +129 -0
  225. package/src/report/react/fixtures.ts +163 -34
  226. package/src/report/react/format.ts +1 -1
  227. package/src/report/react/index.tsx +22 -6
  228. package/src/report/react/render.test.tsx +164 -31
  229. package/src/report/react/styles.css +405 -213
  230. package/src/report/report.test.ts +403 -94
  231. package/src/report/report.ts +29 -11
  232. package/src/report/text/faces.ts +303 -95
  233. package/src/report/text/layout.ts +76 -11
  234. package/src/report/text/table.ts +98 -0
  235. package/src/report/tree.ts +103 -20
  236. package/src/report/types.ts +196 -25
  237. package/src/report/web.ts +46 -19
  238. package/src/results/annotated-source.test.ts +146 -0
  239. package/src/results/annotated-source.ts +128 -0
  240. package/src/results/attempt-evidence.test.ts +158 -0
  241. package/src/results/attempt-evidence.ts +158 -0
  242. package/src/results/attempt-source.ts +48 -0
  243. package/src/results/copy.ts +154 -139
  244. package/src/results/format.ts +54 -47
  245. package/src/results/host-equivalence.test.ts +527 -0
  246. package/src/results/index.ts +56 -14
  247. package/src/results/locator.test.ts +198 -0
  248. package/src/results/locator.ts +0 -0
  249. package/src/results/open.ts +392 -175
  250. package/src/results/results.test.ts +943 -494
  251. package/src/results/select.ts +129 -23
  252. package/src/results/skipped-notice.ts +0 -0
  253. package/src/results/source-hash.ts +28 -0
  254. package/src/results/types.ts +98 -62
  255. package/src/results/writer.ts +304 -176
  256. package/src/runner/attempt.test.ts +170 -0
  257. package/src/runner/attempt.ts +97 -15
  258. package/src/runner/discover.test.ts +65 -0
  259. package/src/runner/discover.ts +6 -2
  260. package/src/runner/eval-source.test.ts +121 -0
  261. package/src/runner/eval-source.ts +45 -0
  262. package/src/runner/fingerprint.ts +54 -1
  263. package/src/runner/remote-sandbox.ts +1 -1
  264. package/src/runner/report.test.ts +6 -8
  265. package/src/runner/report.ts +5 -4
  266. package/src/runner/reporters/artifacts.ts +27 -41
  267. package/src/runner/reporters/braintrust.test.ts +3 -3
  268. package/src/runner/reporters/braintrust.ts +1 -1
  269. package/src/runner/reporters/console.ts +6 -6
  270. package/src/runner/reporters/json.ts +4 -4
  271. package/src/runner/reporters/live.test.ts +56 -0
  272. package/src/runner/reporters/live.ts +36 -10
  273. package/src/runner/reporters/quiet.test.ts +66 -0
  274. package/src/runner/reporters/quiet.ts +49 -0
  275. package/src/runner/reporters/shared.ts +5 -5
  276. package/src/runner/reporters/table.ts +17 -17
  277. package/src/runner/run.ts +61 -102
  278. package/src/runner/types.ts +43 -32
  279. package/src/sandbox/checkpoint.ts +1 -1
  280. package/src/sandbox/docker.ts +15 -2
  281. package/src/sandbox/e2b.ts +11 -5
  282. package/src/sandbox/errors.ts +13 -0
  283. package/src/sandbox/index.ts +3 -3
  284. package/src/sandbox/local-files.ts +1 -1
  285. package/src/sandbox/registry.ts +6 -5
  286. package/src/sandbox/resolve.ts +38 -23
  287. package/src/sandbox/retry.test.ts +68 -0
  288. package/src/sandbox/retry.ts +44 -0
  289. package/src/sandbox/types.ts +69 -28
  290. package/src/sandbox/vercel.ts +17 -3
  291. package/src/scoring/collector.ts +3 -3
  292. package/src/scoring/judge.ts +2 -2
  293. package/src/scoring/scoped.ts +13 -1
  294. package/src/scoring/types.ts +7 -7
  295. package/src/scoring/verdict.ts +4 -4
  296. package/src/shared/aggregate.ts +23 -3
  297. package/src/shared/types.ts +2 -2
  298. package/src/shared/{outcome.ts → verdict.ts} +15 -15
  299. package/src/show/compose.ts +12 -132
  300. package/src/show/index.ts +125 -94
  301. package/src/show/render.ts +660 -160
  302. package/src/show/show.test.ts +472 -253
  303. package/src/source-loc.ts +1 -1
  304. package/src/tty-line.ts +32 -0
  305. package/src/types.ts +2 -2
  306. package/src/util.test.ts +21 -1
  307. package/src/util.ts +5 -1
  308. package/src/view/app/App.tsx +33 -198
  309. package/src/view/app/components/AttemptModal.tsx +6 -6
  310. package/src/view/app/components/CodeView.tsx +2 -2
  311. package/src/view/app/components/CopyControls.tsx +32 -36
  312. package/src/view/app/components/SkippedRunsBanner.tsx +140 -0
  313. package/src/view/app/components/ui/badge.tsx +1 -1
  314. package/src/view/app/components/ui/dialog.tsx +2 -2
  315. package/src/view/app/i18n.ts +40 -31
  316. package/src/view/app/lib/artifact-url.ts +1 -1
  317. package/src/view/app/lib/attempt-route.test.ts +37 -47
  318. package/src/view/app/lib/attempt-route.ts +26 -26
  319. package/src/view/app/lib/rows.ts +9 -112
  320. package/src/view/app/lib/verdict.ts +25 -0
  321. package/src/view/app/main.tsx +9 -12
  322. package/src/view/app/pages/RunsPage.tsx +4 -4
  323. package/src/view/app/pages/TracesPage.tsx +3 -3
  324. package/src/view/app/shared.ts +0 -1
  325. package/src/view/app/types.ts +6 -51
  326. package/src/view/artifact-serving.test.ts +136 -0
  327. package/src/view/client-dist/app.css +1 -1
  328. package/src/view/client-dist/app.js +21 -21
  329. package/src/view/data.test.ts +214 -143
  330. package/src/view/data.ts +166 -120
  331. package/src/view/index.ts +23 -11
  332. package/src/view/server.ts +52 -17
  333. package/src/view/shared/types.ts +22 -31
  334. package/src/view/styles.css +117 -327
  335. package/src/view/view-report.test.ts +146 -98
  336. package/docs-site/zh/guides/sandbox-backends.mdx +0 -75
  337. package/src/report/default-report.tsx +0 -222
  338. package/src/report/dual-face.test.tsx +0 -527
  339. package/src/report/react/CaseList.tsx +0 -70
  340. package/src/view/app/components/CostScoreChart.tsx +0 -147
  341. package/src/view/app/components/ExperimentTable.tsx +0 -284
  342. package/src/view/app/components/GroupSelector.tsx +0 -61
  343. package/src/view/app/components/primitives.tsx +0 -43
  344. package/src/view/app/lib/outcome.ts +0 -82
@@ -0,0 +1,100 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { formatMetricValue } from "../format.js";
3
+ import { DEFAULT_REPORT_LOCALE, countText, localeText, resolveMetricLabel } from "../locale.js";
4
+ import { layoutLabelOffsets, niceTicks } from "./chart-math.js";
5
+ import { colorClassForKey, seriesClassForKey } from "./colors.js";
6
+ import { cx } from "./format.js";
7
+ const WIDTH = 640;
8
+ const HEIGHT = 360;
9
+ const MARGIN = { top: 26, right: 24, bottom: 46, left: 62 };
10
+ const PLOT_W = WIDTH - MARGIN.left - MARGIN.right;
11
+ const PLOT_H = HEIGHT - MARGIN.top - MARGIN.bottom;
12
+ /** 点的直接标签:experiment id 的末段(完整 id 在 <title> 里)。 */
13
+ function pointLabel(key) {
14
+ return key.split("/").filter(Boolean).at(-1) ?? key;
15
+ }
16
+ /**
17
+ * 一根轴:niceTicks 撑出整齐的值域,值 → 像素做线性映射;
18
+ * better: "lower" 时反向,好的一端固定在右 / 上。
19
+ */
20
+ function axisScale(values, better, pixelLo, pixelHi) {
21
+ const ticks = niceTicks(Math.min(...values), Math.max(...values), 5);
22
+ const lo = ticks[0];
23
+ const hi = ticks[ticks.length - 1];
24
+ const scale = (v) => {
25
+ let t = (v - lo) / (hi - lo || 1);
26
+ if (better === "lower")
27
+ t = 1 - t; // 反向:值越低越靠「好」的一端
28
+ return pixelLo + t * (pixelHi - pixelLo);
29
+ };
30
+ return { ticks, scale };
31
+ }
32
+ export function MetricScatter({ data, pointHref, className, locale = DEFAULT_REPORT_LOCALE, }) {
33
+ const missing = data.rows.filter((r) => r.x.value === null || r.y.value === null);
34
+ const drawableRows = data.rows.filter((r) => r.x.value !== null && r.y.value !== null);
35
+ const missingNote = missing.length > 0 ? (_jsx("p", { className: "nre-scatter-missing", title: missing.map((r) => r.key).join(", "), children: countText(locale, "pointsMissing", missing.length) })) : null;
36
+ const xLabel = resolveMetricLabel(data.x.label, locale, data.x.key);
37
+ const yLabel = resolveMetricLabel(data.y.label, locale, data.y.key);
38
+ // 0 个可画点:x/y 指标没有可用数据 —— 明确说缺哪两个指标,不画一张空坐标系
39
+ // (与表格的「绝不画 0」同态度)。
40
+ if (drawableRows.length === 0) {
41
+ return (_jsxs("figure", { className: cx("nre", "nre-metric-scatter", className), children: [_jsx("p", { className: "nre-scatter-empty nre-missing", children: localeText(locale, "scatter.noData", { x: xLabel, y: yLabel }) }), missingNote] }));
42
+ }
43
+ // 恰好 1 个可画点:成本 × 通过率的对比至少要两个实验,单点不成图 —— 显式说清而不是画个孤点。
44
+ if (drawableRows.length === 1) {
45
+ return (_jsxs("figure", { className: cx("nre", "nre-metric-scatter", className), children: [_jsx("p", { className: "nre-scatter-empty", children: localeText(locale, "scatter.needTwo", { x: xLabel, y: yLabel }) }), missingNote] }));
46
+ }
47
+ const axisLabel = (label, col) => `${label}${col.unit ? `(${col.unit})` : ""}`;
48
+ const xScale = axisScale(drawableRows.map((r) => r.x.value), data.x.better, MARGIN.left, MARGIN.left + PLOT_W);
49
+ // y 像素轴向下增长:better:"higher" 高值在上 → 映射到 [bottom, top];"lower" 由 axisScale 反向后同样落到上方
50
+ const yScale = axisScale(drawableRows.map((r) => r.y.value), data.y.better, MARGIN.top + PLOT_H, MARGIN.top);
51
+ const points = drawableRows.map((r) => {
52
+ const xValue = r.x.value;
53
+ const yValue = r.y.value;
54
+ return {
55
+ key: r.key,
56
+ series: r.series,
57
+ label: pointLabel(r.key),
58
+ xValue,
59
+ yValue,
60
+ // hover 内容:experiment(点键)+ 系列(series,CostPassRateComparison 传的是 agent 维度,
61
+ // 有则加一行;无系列的散点没有这行)+ 两轴 display 与 samples/total(docs/feature/reports/library.md「MetricScatter」行为清单)
62
+ title: `${r.key}${r.series !== undefined ? `\n${r.series}` : ""}\n${xLabel}: ${r.x.display}(${r.x.samples}/${r.x.total})\n${yLabel}: ${r.y.display}(${r.y.samples}/${r.y.total})`,
63
+ px: xScale.scale(xValue),
64
+ py: yScale.scale(yValue),
65
+ };
66
+ });
67
+ // 同系列的点按 x 值排序连线;无系列的点只画点不连线
68
+ const seriesOrder = [];
69
+ const bySeries = new Map();
70
+ for (const p of points) {
71
+ if (p.series === undefined)
72
+ continue;
73
+ if (!bySeries.has(p.series)) {
74
+ bySeries.set(p.series, []);
75
+ seriesOrder.push(p.series);
76
+ }
77
+ bySeries.get(p.series).push(p);
78
+ }
79
+ for (const list of bySeries.values())
80
+ list.sort((a, b) => a.xValue - b.xValue);
81
+ // 直接标签的防撞布局:靠右的点把标签锚到左侧,重叠的往下推、补 leader line
82
+ const positions = points.map((p) => {
83
+ const anchorLeft = p.px >= MARGIN.left + PLOT_W * 0.72;
84
+ return { cx: p.px, cy: p.py, anchorLeft, width: p.label.length * 6.4 + 10 };
85
+ });
86
+ const labelOffsets = layoutLabelOffsets(positions);
87
+ return (_jsxs("figure", { className: cx("nre", "nre-metric-scatter", className), children: [_jsxs("svg", { className: "nre-scatter-svg", viewBox: `0 0 ${WIDTH} ${HEIGHT}`, role: "img", "aria-label": `${axisLabel(xLabel, data.x)} × ${axisLabel(yLabel, data.y)}`, children: [_jsxs("g", { className: "nre-scatter-grid", children: [yScale.ticks.map((tick) => (_jsx("line", { x1: MARGIN.left, x2: MARGIN.left + PLOT_W, y1: yScale.scale(tick), y2: yScale.scale(tick) }, `gy${tick}`))), xScale.ticks.map((tick) => (_jsx("line", { y1: MARGIN.top, y2: MARGIN.top + PLOT_H, x1: xScale.scale(tick), x2: xScale.scale(tick) }, `gx${tick}`)))] }), _jsx("text", { className: "nre-scatter-better-hint", x: MARGIN.left + PLOT_W - 6, y: MARGIN.top + 14, textAnchor: "end", children: localeText(locale, "scatter.betterHint") }), _jsx("g", { className: "nre-scatter-axis nre-scatter-axis-y", children: yScale.ticks.map((tick) => (_jsx("text", { className: "nre-scatter-tick", x: MARGIN.left - 8, y: yScale.scale(tick) + 3, textAnchor: "end", children: formatMetricValue(tick, data.y.unit) }, `ay${tick}`))) }), _jsx("g", { className: "nre-scatter-axis nre-scatter-axis-x", children: xScale.ticks.map((tick) => (_jsx("text", { className: "nre-scatter-tick", x: xScale.scale(tick), y: MARGIN.top + PLOT_H + 16, textAnchor: "middle", children: formatMetricValue(tick, data.x.unit) }, `ax${tick}`))) }), _jsx("text", { className: "nre-scatter-xlabel", x: MARGIN.left + PLOT_W / 2, y: HEIGHT - 8, textAnchor: "middle", children: axisLabel(xLabel, data.x) }), _jsx("text", { className: "nre-scatter-ylabel", x: 14, y: MARGIN.top + PLOT_H / 2, textAnchor: "middle", transform: `rotate(-90 14 ${MARGIN.top + PLOT_H / 2})`, children: axisLabel(yLabel, data.y) }), seriesOrder.map((series) => {
88
+ const list = bySeries.get(series);
89
+ if (list.length < 2)
90
+ return null;
91
+ return (_jsx("polyline", { className: cx("nre-scatter-line", seriesClassForKey(series)), "data-series": series, points: list.map((p) => `${p.px},${p.py}`).join(" "), fill: "none" }, series));
92
+ }), points.map((p, i) => {
93
+ const { anchorLeft } = positions[i];
94
+ const labelX = p.px + (anchorLeft ? -10 : 10);
95
+ const labelY = p.py + 4 + labelOffsets[i];
96
+ const group = (_jsxs("g", { className: cx("nre-scatter-point", p.series !== undefined ? seriesClassForKey(p.series) : "nre-series-none"), "data-key": p.key, children: [_jsx("title", { children: p.title }), labelOffsets[i] !== 0 && _jsx("line", { className: "nre-leader", x1: p.px, y1: p.py, x2: labelX, y2: labelY - 4 }), _jsx("circle", { className: "nre-scatter-hit", cx: p.px, cy: p.py, r: 12 }), _jsx("circle", { className: "nre-scatter-dot", cx: p.px, cy: p.py, r: 4.5 }), _jsx("text", { className: "nre-scatter-point-label", x: labelX, y: labelY, textAnchor: anchorLeft ? "end" : "start", children: p.label })] }));
97
+ const row = drawableRows[i];
98
+ return pointHref ? (_jsx("a", { className: "nre-scatter-point-link", href: pointHref(row), children: group }, p.key)) : (_jsx("g", { children: group }, p.key));
99
+ })] }), seriesOrder.length > 0 && (_jsx("figcaption", { className: "nre-scatter-legend", children: seriesOrder.map((series) => (_jsx("span", { className: cx("nre-legend-key", "nre-key", colorClassForKey(series)), children: series }, series))) })), missingNote] }));
100
+ }
@@ -0,0 +1,11 @@
1
+ import type { ReactElement } from "react";
2
+ import type { TableData } from "../types.ts";
3
+ import type { AttemptLocator } from "../../results/locator.ts";
4
+ import { type ReportLocale } from "../locale.ts";
5
+ export declare function MetricTable({ data, attemptHref, filter, className, locale, }: {
6
+ data: TableData;
7
+ attemptHref?: (locator: AttemptLocator) => string;
8
+ filter?: boolean;
9
+ className?: string;
10
+ locale?: ReportLocale;
11
+ }): ReactElement;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { DEFAULT_REPORT_LOCALE, localeText, resolveMetricLabel } from "../locale.js";
3
+ import { MetricCellView } from "./cell.js";
4
+ import { colorClassForKey } from "./colors.js";
5
+ import { cx } from "./format.js";
6
+ /** verdict 计票 pill(「3 passed / 1 failed」):非零判定各一枚,全零如实空。 */
7
+ function VerdictTally({ verdicts, locale, }) {
8
+ const kinds = ["passed", "failed", "errored", "skipped"].filter((k) => verdicts[k] > 0);
9
+ return (_jsxs("span", { className: "nre-verdict-tally", children: [kinds.map((kind) => (_jsxs("span", { className: cx("nre-verdict-pill", `nre-verdict-${kind}`), children: [verdicts[kind], " ", localeText(locale, `verdict.${kind}`)] }, kind))), kinds.length === 0 && _jsx("span", { className: "nre-missing", children: "\u2014" })] }));
10
+ }
11
+ export function MetricTable({ data, attemptHref, filter, className, locale = DEFAULT_REPORT_LOCALE, }) {
12
+ const hasMeta = data.rows.some((row) => row.meta !== undefined);
13
+ const hasModel = data.rows.some((row) => row.meta?.model !== undefined);
14
+ const hasVerdicts = data.rows.some((row) => row.meta?.verdicts !== undefined);
15
+ const table = (_jsxs("table", { className: cx("nre", "nre-metric-table", !filter && className), children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", className: "nre-dimension", "data-nre-sort": "", children: data.dimension }), hasMeta && hasModel && (_jsx("th", { scope: "col", className: "nre-meta-col", "data-nre-sort": "", children: localeText(locale, "table.model") })), hasMeta && (_jsx("th", { scope: "col", className: "nre-meta-col", "data-nre-sort": "", children: localeText(locale, "table.agent") })), data.columns.map((col) => (_jsxs("th", { scope: "col", className: "nre-metric-col", "data-nre-sort": "", children: [resolveMetricLabel(col.label, locale, col.key), col.unit && _jsxs("span", { className: "nre-unit", children: ["(", col.unit, ")"] }), col.better && (_jsx("span", { className: "nre-better", title: localeText(locale, col.better === "higher" ? "table.higherBetter" : "table.lowerBetter"), children: col.better === "higher" ? "↑" : "↓" }))] }, col.key))), hasVerdicts && (_jsx("th", { scope: "col", className: "nre-verdicts-col", "data-nre-sort": "", children: localeText(locale, "table.verdicts") }))] }) }), _jsx("tbody", { children: data.rows.map((row) => (_jsxs("tr", { children: [_jsxs("th", { scope: "row", className: cx("nre-row-key", "nre-key", colorClassForKey(row.key)), "data-sort-value": row.key, children: [row.key, row.meta?.evals !== undefined && (_jsxs("div", { className: "nre-row-meta-sub", children: [localeText(locale, "overview.evalsCount", { n: row.meta.evals }), row.meta.attempts !== undefined && row.meta.attempts > row.meta.evals
16
+ ? ` · ${localeText(locale, "overview.attemptsCount", { n: row.meta.attempts })}`
17
+ : "", row.meta.lastRunAt ? ` · ${localeText(locale, "latestRun", { run: row.meta.lastRunAt })}` : ""] }))] }), hasMeta && hasModel && (_jsx("td", { className: "nre-td nre-meta-cell", "data-sort-value": row.meta?.model ?? "", children: row.meta?.model ?? _jsx("span", { className: "nre-missing", children: "\u2014" }) })), hasMeta && (_jsx("td", { className: "nre-td nre-meta-cell", "data-sort-value": row.meta?.agent ?? "", children: row.meta?.agent ?? _jsx("span", { className: "nre-missing", children: "\u2014" }) })), data.columns.map((col) => {
18
+ const cell = row.cells[col.key];
19
+ return (_jsx("td", { className: "nre-td", "data-sort-value": cell?.value ?? "", children: cell ? (_jsx(MetricCellView, { cell: cell, attemptHref: attemptHref, locale: locale })) : (_jsx("span", { className: "nre-empty" })) }, col.key));
20
+ }), hasVerdicts && (_jsx("td", { className: "nre-td nre-verdicts-cell", "data-sort-value": row.meta?.verdicts?.passed ?? "", children: row.meta?.verdicts ? (_jsx(VerdictTally, { verdicts: row.meta.verdicts, locale: locale })) : (_jsx("span", { className: "nre-missing", children: "\u2014" })) }))] }, row.key))) })] }));
21
+ if (!filter)
22
+ return table;
23
+ // 过滤输入框渲染在表格前(同一个 wrap 里),enhance.js 经 data-nre-filter 接管;
24
+ // 无 JS 时静默无功能,表格内容依旧完整。
25
+ return (_jsxs("div", { className: cx("nre", "nre-metric-table-wrap", className), children: [_jsx("input", { className: "nre-filter", "data-nre-filter": "", type: "search", placeholder: localeText(locale, "table.filterPlaceholder") }), table] }));
26
+ }
@@ -0,0 +1,8 @@
1
+ import type { ReactElement } from "react";
2
+ import type { OverviewData } from "../types.ts";
3
+ import { type ReportLocale } from "../locale.ts";
4
+ export declare function RunOverview({ data, className, locale, }: {
5
+ data: OverviewData;
6
+ className?: string;
7
+ locale?: ReportLocale;
8
+ }): ReactElement;
@@ -0,0 +1,12 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { DEFAULT_REPORT_LOCALE, localeText } from "../locale.js";
3
+ import { cx, formatDurationMs, formatUSD } from "./format.js";
4
+ export function RunOverview({ data, className, locale = DEFAULT_REPORT_LOCALE, }) {
5
+ const { totals } = data;
6
+ const missing = _jsx("span", { className: "nre-missing", children: localeText(locale, "cell.missing") });
7
+ // 通过率只渲染 computeCell 算好的 MetricCell,不现场重算——覆盖率角标复用
8
+ // MetricCellView 的缺数据/coverage 语义(缺数据 = 显式文案,绝不画 0%)
9
+ const { passRate } = totals;
10
+ const passRateNode = passRate.value === null ? (missing) : (_jsxs(_Fragment, { children: [passRate.display, passRate.samples < passRate.total && (_jsxs("sup", { className: "nre-coverage", title: localeText(locale, "cell.coverageTitle", { samples: passRate.samples, total: passRate.total }), children: [passRate.samples, "/", passRate.total] }))] }));
11
+ return (_jsxs("header", { className: cx("nre", "nre-overview", className), children: [_jsxs("dl", { className: "nre-kpis", children: [_jsxs("div", { className: "nre-kpi", children: [_jsx("dt", { children: localeText(locale, "overview.snapshots") }), _jsx("dd", { children: data.snapshots.length })] }), _jsxs("div", { className: "nre-kpi", children: [_jsx("dt", { children: localeText(locale, "overview.evals") }), _jsx("dd", { children: totals.evals })] }), _jsxs("div", { className: "nre-kpi", children: [_jsx("dt", { children: localeText(locale, "overview.attempts") }), _jsx("dd", { children: totals.attempts })] }), _jsxs("div", { className: "nre-kpi", children: [_jsx("dt", { children: localeText(locale, "overview.passRate") }), _jsx("dd", { children: passRateNode })] }), _jsxs("div", { className: "nre-kpi", children: [_jsx("dt", { children: localeText(locale, "overview.totalCost") }), _jsx("dd", { children: totals.costUSD === null ? missing : formatUSD(totals.costUSD) })] }), _jsxs("div", { className: "nre-kpi", children: [_jsx("dt", { children: localeText(locale, "overview.totalDuration") }), _jsx("dd", { children: formatDurationMs(totals.durationMs) })] })] }), _jsxs("p", { className: "nre-verdicts", children: [_jsxs("span", { className: "nre-verdict nre-verdict-passed", children: [localeText(locale, "verdict.passed"), " ", totals.passed] }), _jsxs("span", { className: "nre-verdict nre-verdict-failed", children: [localeText(locale, "verdict.failed"), " ", totals.failed] }), _jsxs("span", { className: "nre-verdict nre-verdict-errored", children: [localeText(locale, "verdict.errored"), " ", totals.errored] }), _jsxs("span", { className: "nre-verdict nre-verdict-skipped", children: [localeText(locale, "verdict.skipped"), " ", totals.skipped] })] }), _jsxs("p", { className: "nre-source", children: [localeText(locale, "overview.source", { n: data.snapshots.length }), data.snapshots.map((s) => (_jsxs("span", { className: "nre-source-snapshot", children: [s.experimentId, "(", s.agent, s.model ? ` · ${s.model}` : "", ")@ ", s.startedAt] }, `${s.experimentId}@${s.startedAt}`)))] }), data.warnings.length > 0 && (_jsx("ul", { className: "nre-warnings", children: data.warnings.map((w, i) => (_jsx("li", { className: "nre-warning", "data-kind": w.kind, children: w.message }, i))) }))] }));
12
+ }
@@ -0,0 +1,8 @@
1
+ import type { ReactElement } from "react";
2
+ import type { ScoreboardData } from "../types.ts";
3
+ import { type ReportLocale } from "../locale.ts";
4
+ export declare function Scoreboard({ data, className, locale, }: {
5
+ data: ScoreboardData;
6
+ className?: string;
7
+ locale?: ReportLocale;
8
+ }): ReactElement;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { DEFAULT_REPORT_LOCALE, countText, localeText } from "../locale.js";
3
+ import { colorClassForKey } from "./colors.js";
4
+ import { cx } from "./format.js";
5
+ export function Scoreboard({ data, className, locale = DEFAULT_REPORT_LOCALE, }) {
6
+ // 科目列 = 各行 subjects 的并集,按首次出现顺序;固定分母下各行本应一致,这里防御性合并
7
+ const subjectKeys = [];
8
+ for (const row of data.rows) {
9
+ for (const subject of row.subjects) {
10
+ if (!subjectKeys.includes(subject.key))
11
+ subjectKeys.push(subject.key);
12
+ }
13
+ }
14
+ return (_jsxs("section", { className: cx("nre", "nre-scoreboard", className), children: [_jsxs("table", { className: "nre-scoreboard-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", className: "nre-dimension", children: data.dimension }), _jsxs("th", { scope: "col", className: "nre-total-col", children: [localeText(locale, "scoreboard.total"), _jsxs("span", { className: "nre-full-marks", children: ["/ ", data.fullMarks] })] }), subjectKeys.map((key) => (_jsx("th", { scope: "col", className: "nre-subject-col", children: key }, key)))] }) }), _jsx("tbody", { children: data.rows.map((row) => (_jsxs("tr", { children: [_jsx("th", { scope: "row", className: cx("nre-row-key", "nre-key", colorClassForKey(row.key)), children: row.key }), _jsx("td", { className: "nre-total", children: row.total.display }), subjectKeys.map((key) => {
15
+ const subject = row.subjects.find((s) => s.key === key);
16
+ if (!subject)
17
+ return _jsx("td", { className: "nre-td-empty" }, key);
18
+ return (_jsxs("td", { className: "nre-subject", children: [_jsxs("span", { className: "nre-subject-score", title: localeText(locale, "scoreboard.subjectTitle", {
19
+ evals: subject.evals,
20
+ earned: subject.earned,
21
+ possible: subject.possible,
22
+ }), children: [subject.earned, "/", subject.possible] }), subject.missing > 0 && (_jsx("span", { className: "nre-subject-missing", children: countText(locale, "scoreboard.missing", subject.missing) }))] }, key));
23
+ })] }, row.key))) })] }), _jsxs("p", { className: "nre-weights", children: [localeText(locale, "scoreboard.weights"), " ", data.weights.length === 0
24
+ ? localeText(locale, "scoreboard.allWeights")
25
+ : data.weights.map((w) => (_jsxs("span", { className: "nre-weight", children: [w.prefix, " \u00D7", w.weight] }, w.prefix))), data.weights.length > 0 && _jsx("span", { className: "nre-weight-rest", children: localeText(locale, "scoreboard.othersWeight") })] })] }));
26
+ }
@@ -0,0 +1,9 @@
1
+ import type { ReactElement } from "react";
2
+ import type { MetricCell } from "../types.ts";
3
+ import type { AttemptLocator } from "../../results/locator.ts";
4
+ import { type ReportLocale } from "../locale.ts";
5
+ export declare function MetricCellView({ cell, attemptHref, locale, }: {
6
+ cell: MetricCell;
7
+ attemptHref?: (locator: AttemptLocator) => string;
8
+ locale?: ReportLocale;
9
+ }): ReactElement;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { DEFAULT_REPORT_LOCALE, localeText } from "../locale.js";
3
+ export function MetricCellView({ cell, attemptHref, locale = DEFAULT_REPORT_LOCALE, }) {
4
+ // 全 null(没有任何有效样本)→ 缺数据文案,绝不画 0;total 仍如实入 title
5
+ if (cell.value === null) {
6
+ return (_jsx("span", { className: "nre-cell nre-cell-missing", children: _jsx("span", { className: "nre-missing", title: localeText(locale, "cell.noneMeasurableTitle", { total: cell.total }), children: localeText(locale, "cell.missing") }) }));
7
+ }
8
+ return (_jsxs("span", { className: "nre-cell", children: [_jsx("span", { className: "nre-value", title: localeText(locale, "cell.measuredTitle", { samples: cell.samples, total: cell.total }), children: cell.display }), cell.samples < cell.total && (_jsxs("sup", { className: "nre-coverage", title: localeText(locale, "cell.coverageTitle", { samples: cell.samples, total: cell.total }), children: [cell.samples, "/", cell.total] })), attemptHref && cell.refs && cell.refs.length > 0 && (_jsx("span", { className: "nre-refs", children: cell.refs.map((locator, i) => (_jsxs("a", { className: "nre-ref", href: attemptHref(locator), children: ["#", i + 1] }, locator))) }))] }));
9
+ }
@@ -0,0 +1,14 @@
1
+ /** [min, max] 区间上生成 count 个左右的“整齐”刻度(Heckbert nice-numbers)。 */
2
+ export declare function niceTicks(min: number, max: number, count?: number): number[];
3
+ export interface LabelInput {
4
+ cx: number;
5
+ cy: number;
6
+ width: number;
7
+ /** true = 文字锚在点左侧(text-anchor: end),false = 锚在右侧(text-anchor: start)。 */
8
+ anchorLeft: boolean;
9
+ }
10
+ /**
11
+ * 贪心地把互相重叠的直接标签往下推开(按 y 排序、逐个查重叠再下移),点本身位置不变。
12
+ * 散点常见同分不同价的聚簇,这一步避免标签叠成一团糊字。
13
+ */
14
+ export declare function layoutLabelOffsets(items: LabelInput[], lineHeight?: number, padX?: number): number[];
@@ -0,0 +1,68 @@
1
+ // 通用图表数值工具:round-number 刻度生成与标签防撞布局,零依赖纯函数。
2
+ // 从 src/view/app/lib/chart.ts 迁来(view 侧从这里 re-export),报告组件与 view 共用同一份。
3
+ function niceNumber(range, round) {
4
+ if (range <= 0)
5
+ return 1;
6
+ const exponent = Math.floor(Math.log10(range));
7
+ const fraction = range / 10 ** exponent;
8
+ let niceFraction;
9
+ if (round) {
10
+ if (fraction < 1.5)
11
+ niceFraction = 1;
12
+ else if (fraction < 3)
13
+ niceFraction = 2;
14
+ else if (fraction < 7)
15
+ niceFraction = 5;
16
+ else
17
+ niceFraction = 10;
18
+ }
19
+ else {
20
+ if (fraction <= 1)
21
+ niceFraction = 1;
22
+ else if (fraction <= 2)
23
+ niceFraction = 2;
24
+ else if (fraction <= 5)
25
+ niceFraction = 5;
26
+ else
27
+ niceFraction = 10;
28
+ }
29
+ return niceFraction * 10 ** exponent;
30
+ }
31
+ /** [min, max] 区间上生成 count 个左右的“整齐”刻度(Heckbert nice-numbers)。 */
32
+ export function niceTicks(min, max, count = 5) {
33
+ if (min === max) {
34
+ min -= 1;
35
+ max += 1;
36
+ }
37
+ const range = niceNumber(max - min, false);
38
+ const step = niceNumber(range / Math.max(1, count - 1), true);
39
+ const niceMin = Math.floor(min / step) * step;
40
+ const niceMax = Math.ceil(max / step) * step;
41
+ const ticks = [];
42
+ for (let v = niceMin; v <= niceMax + step * 0.5; v += step)
43
+ ticks.push(Number(v.toFixed(10)));
44
+ return ticks;
45
+ }
46
+ /**
47
+ * 贪心地把互相重叠的直接标签往下推开(按 y 排序、逐个查重叠再下移),点本身位置不变。
48
+ * 散点常见同分不同价的聚簇,这一步避免标签叠成一团糊字。
49
+ */
50
+ export function layoutLabelOffsets(items, lineHeight = 14, padX = 4) {
51
+ const offsets = new Array(items.length).fill(0);
52
+ const order = [...items.keys()].sort((a, b) => items[a].cy - items[b].cy || items[a].cx - items[b].cx);
53
+ const placed = [];
54
+ for (const i of order) {
55
+ const it = items[i];
56
+ const x0 = it.anchorLeft ? it.cx - it.width : it.cx;
57
+ const x1 = it.anchorLeft ? it.cx : it.cx + it.width;
58
+ let y = it.cy;
59
+ let guard = 0;
60
+ while (guard < 40 && placed.some((p) => x0 < p.x1 + padX && x1 > p.x0 - padX && Math.abs(y - p.y) < lineHeight)) {
61
+ y += lineHeight;
62
+ guard++;
63
+ }
64
+ placed.push({ x0, x1, y });
65
+ offsets[i] = y - it.cy;
66
+ }
67
+ return offsets;
68
+ }
@@ -0,0 +1,14 @@
1
+ /** 固定调色板(浅色主题值);下标即 nre-cN / nre-series-cN 的 N,与 styles.css 的 --nre-cN 逐个对应。 */
2
+ export declare const NRE_PALETTE: readonly ["#2a78d6", "#1baf7a", "#eda100", "#008300", "#e34948", "#eb6834"];
3
+ export declare const NRE_PALETTE_SIZE: 6;
4
+ /** FNV-1a 32 位散列:输入相同永远得到相同下标,与运行顺序无关。 */
5
+ export declare function colorIndexForKey(key: string): number;
6
+ /** 键对应的稳定 class 名("nre-c3"),配 styles.css 的 .nre-cN 上文字色。 */
7
+ export declare function colorClassForKey(key: string): string;
8
+ /**
9
+ * 键对应的系列 class 名("nre-series-c3"):挂在 SVG 图形元素(线/点/柱/系列名)上,
10
+ * styles.css 用它设置 --nre-series,fill/stroke 走 var —— 深色主题下图表随之切换。
11
+ */
12
+ export declare function seriesClassForKey(key: string): string;
13
+ /** 键对应的十六进制颜色(浅色主题值);渲染面优先类名,这里只留给不经 CSS 的消费方。 */
14
+ export declare function colorHexForKey(key: string): string;
@@ -0,0 +1,45 @@
1
+ // 跨块配色一致(docs/feature/reports/architecture.md「静态网页」):
2
+ // 系列/维度键 → 固定调色板下标,用稳定散列而不是「按出现顺序分配」。
3
+ // 这样同一个 agent 在 scatter 的线、DeltaTable 的行、matrix 的列头永远同色,
4
+ // 不需要 Provider、不需要手工配置,甚至不需要两个组件见过同一份数据。
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」的消费方。改色时两边一起改。
11
+ /** 固定调色板(浅色主题值);下标即 nre-cN / nre-series-cN 的 N,与 styles.css 的 --nre-cN 逐个对应。 */
12
+ export const NRE_PALETTE = [
13
+ "#2a78d6", // c0 蓝(dark: #3987e5)
14
+ "#1baf7a", // c1 绿(dark: #199e70)
15
+ "#eda100", // c2 琥珀(dark: #c98500)
16
+ "#008300", // c3 深绿(dark: #008300)
17
+ "#e34948", // c4 红(dark: #e66767)
18
+ "#eb6834", // c5 橙(dark: #d95926)
19
+ ];
20
+ export const NRE_PALETTE_SIZE = NRE_PALETTE.length;
21
+ /** FNV-1a 32 位散列:输入相同永远得到相同下标,与运行顺序无关。 */
22
+ export function colorIndexForKey(key) {
23
+ let hash = 0x811c9dc5;
24
+ for (let i = 0; i < key.length; i++) {
25
+ hash ^= key.charCodeAt(i);
26
+ // FNV prime 乘法,拆成移位加法保持 32 位整数运算
27
+ hash = (hash + ((hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24))) >>> 0;
28
+ }
29
+ return hash % NRE_PALETTE_SIZE;
30
+ }
31
+ /** 键对应的稳定 class 名("nre-c3"),配 styles.css 的 .nre-cN 上文字色。 */
32
+ export function colorClassForKey(key) {
33
+ return `nre-c${colorIndexForKey(key)}`;
34
+ }
35
+ /**
36
+ * 键对应的系列 class 名("nre-series-c3"):挂在 SVG 图形元素(线/点/柱/系列名)上,
37
+ * styles.css 用它设置 --nre-series,fill/stroke 走 var —— 深色主题下图表随之切换。
38
+ */
39
+ export function seriesClassForKey(key) {
40
+ return `nre-series-c${colorIndexForKey(key)}`;
41
+ }
42
+ /** 键对应的十六进制颜色(浅色主题值);渲染面优先类名,这里只留给不经 CSS 的消费方。 */
43
+ export function colorHexForKey(key) {
44
+ return NRE_PALETTE[colorIndexForKey(key)];
45
+ }
@@ -0,0 +1,19 @@
1
+ import type { AttemptListItem, DeltaData, EvalListItem, ExperimentListItem, GroupSummaryData, LineData, MatrixData, MetricColumn, OverviewData, ScatterData, ScoreboardData, TableData } from "../types.ts";
2
+ export declare const passRateColumn: MetricColumn;
3
+ export declare const codeLinesColumn: MetricColumn;
4
+ export declare const costColumn: MetricColumn;
5
+ export declare const overviewData: OverviewData;
6
+ export declare const overviewWithCost: OverviewData;
7
+ /** GroupSummary.data 的产物形态:eval 级折叠计票 + 旧 GroupSelector 口径的通过率(包成 MetricCell)。 */
8
+ export declare const groupSummaryData: GroupSummaryData;
9
+ export declare const tableData: TableData<"pass-rate" | "code-lines">;
10
+ /** rows: "experiment" 的榜单形态:行携带 agent/model 元信息与 eval 级折叠计票。 */
11
+ export declare const tableDataWithMeta: TableData<"pass-rate">;
12
+ export declare const matrixData: MatrixData;
13
+ export declare const scoreboardData: ScoreboardData;
14
+ export declare const scatterData: ScatterData;
15
+ export declare const lineData: LineData;
16
+ export declare const deltaData: DeltaData<"pass-rate" | "cost">;
17
+ export declare const attemptListItems: AttemptListItem[];
18
+ export declare const evalListItems: EvalListItem[];
19
+ export declare const experimentListItems: ExperimentListItem[];