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,173 @@
1
+ // text 面的排版工具:显示宽度(CJK 记 2 列)、填充、折行、对齐列、字符条形。
2
+ // 全部纯函数、零依赖;niceeval show 的终端输出与 Row 分栏都建立在这几样上。
3
+ //
4
+ // 其中七个是公开面(`niceeval/report` 以 stringWidth / padEnd / padStart / wrapText /
5
+ // indent / bar / columns 的名字导出,见 ../index.ts):自定义组件的 text 面必须拿到与
6
+ // 官方组件同一把尺子,否则「用户组件与官方组件对等」是假的。它们的 TSDoc 就是公开文档,
7
+ // 改签名即破坏公开面。renderAlignedRows 不导出 —— 表格的能力由 <Table> 原语承担。
8
+ /** 终端显示宽度:CJK / 全角记 2 列,其余记 1。启发式覆盖常用区段,够对齐表格用。 */
9
+ export function charDisplayWidth(codePoint) {
10
+ if ((codePoint >= 0x1100 && codePoint <= 0x115f) ||
11
+ (codePoint >= 0x2e80 && codePoint <= 0x303e) ||
12
+ (codePoint >= 0x3041 && codePoint <= 0x33ff) ||
13
+ (codePoint >= 0x3400 && codePoint <= 0x4dbf) ||
14
+ (codePoint >= 0x4e00 && codePoint <= 0x9fff) ||
15
+ (codePoint >= 0xa000 && codePoint <= 0xa4cf) ||
16
+ (codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
17
+ (codePoint >= 0xf900 && codePoint <= 0xfaff) ||
18
+ (codePoint >= 0xfe30 && codePoint <= 0xfe4f) ||
19
+ (codePoint >= 0xff00 && codePoint <= 0xff60) ||
20
+ (codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
21
+ (codePoint >= 0x20000 && codePoint <= 0x3fffd)) {
22
+ return 2;
23
+ }
24
+ return 1;
25
+ }
26
+ /**
27
+ * 一段文本在终端里占几列:CJK / 全角字符记 2 列,其余记 1 列。
28
+ *
29
+ * 对齐终端输出用它,不要用 `String.prototype.length`——`length` 数的是 UTF-16 码元,
30
+ * 一个汉字占 2 个显示列却只算 1 个码元,中文一进来列就错位。
31
+ *
32
+ * @param text 任意文本。
33
+ * @returns 显示列数。
34
+ */
35
+ export function stringWidth(text) {
36
+ let width = 0;
37
+ for (const ch of text)
38
+ width += charDisplayWidth(ch.codePointAt(0));
39
+ return width;
40
+ }
41
+ /**
42
+ * 按显示宽度在右侧补空格(左对齐)。超宽不截断,如实溢出。
43
+ *
44
+ * `String.prototype.padEnd` 的显示宽度版:内容含中文时才对得齐。
45
+ *
46
+ * @param text 要补齐的文本。
47
+ * @param width 目标显示宽度。
48
+ * @returns 补齐后的文本。
49
+ */
50
+ export function padDisplay(text, width) {
51
+ const gap = width - stringWidth(text);
52
+ return gap > 0 ? text + " ".repeat(gap) : text;
53
+ }
54
+ /**
55
+ * 按显示宽度在左侧补空格(右对齐)。超宽不截断,如实溢出。
56
+ *
57
+ * 数字列靠它对齐小数点。
58
+ *
59
+ * @param text 要补齐的文本。
60
+ * @param width 目标显示宽度。
61
+ * @returns 补齐后的文本。
62
+ */
63
+ export function padStartDisplay(text, width) {
64
+ const gap = width - stringWidth(text);
65
+ return gap > 0 ? " ".repeat(gap) + text : text;
66
+ }
67
+ /**
68
+ * 按显示宽度折行:优先在空格处断,单个词超宽(URL / 中文长句)按列宽硬断。
69
+ *
70
+ * @param text 要折行的文本(`\n` 分段)。
71
+ * @param width 每行的最大显示宽度。
72
+ * @returns 折好的行;至少一行(空串输入 → `[""]`)。
73
+ */
74
+ export function wrapDisplay(text, width) {
75
+ const max = Math.max(4, width);
76
+ const lines = [];
77
+ for (const paragraph of text.split("\n")) {
78
+ let line = "";
79
+ let lineWidth = 0;
80
+ const flush = () => {
81
+ lines.push(line);
82
+ line = "";
83
+ lineWidth = 0;
84
+ };
85
+ for (const word of paragraph.split(" ")) {
86
+ const wordWidth = stringWidth(word);
87
+ if (lineWidth > 0 && lineWidth + 1 + wordWidth > max)
88
+ flush();
89
+ if (wordWidth > max) {
90
+ // 单词本身超宽(URL / 中文长句):逐字符硬断
91
+ for (const ch of word) {
92
+ const w = charDisplayWidth(ch.codePointAt(0));
93
+ if (lineWidth + w > max)
94
+ flush();
95
+ line += ch;
96
+ lineWidth += w;
97
+ }
98
+ continue;
99
+ }
100
+ if (lineWidth > 0) {
101
+ line += " ";
102
+ lineWidth += 1;
103
+ }
104
+ line += word;
105
+ lineWidth += wordWidth;
106
+ }
107
+ flush();
108
+ }
109
+ return lines.length > 0 ? lines : [""];
110
+ }
111
+ /**
112
+ * 给一整块文本的每行加同一段前缀(嵌套块缩进)。空行不加,不留尾随空格。
113
+ *
114
+ * @param block 多行文本。
115
+ * @param indent 每行的前缀,通常是若干空格。
116
+ * @returns 缩进后的文本块。
117
+ */
118
+ export function indentBlock(block, indent) {
119
+ return block
120
+ .split("\n")
121
+ .map((line) => (line.length > 0 ? indent + line : line))
122
+ .join("\n");
123
+ }
124
+ /**
125
+ * 对齐列渲染:每列宽 = 该列最宽格的显示宽度,列间 3 空格,行尾不留空白。首行是表头。
126
+ * `align[c]` 给第 c 列的对齐方向,缺省 `"left"`。
127
+ *
128
+ * 内部件:公开面是 <Table> 原语(见 ./table.ts 的 renderTableText,它先按 ctx.width
129
+ * 定好列宽再调这里)。不导出到 `niceeval/report` —— 两条并行的建表路径只会让作者选错。
130
+ */
131
+ export function renderAlignedRows(rows, align = [], separator = " ") {
132
+ const columnCount = Math.max(...rows.map((r) => r.length), 0);
133
+ const widths = [];
134
+ for (let c = 0; c < columnCount; c++) {
135
+ widths.push(Math.max(...rows.map((r) => stringWidth(r[c] ?? ""))));
136
+ }
137
+ return rows
138
+ .map((row) => row
139
+ .map((cell, c) => (align[c] === "right" ? padStartDisplay(cell, widths[c]) : padDisplay(cell, widths[c])))
140
+ .join(separator)
141
+ .replace(/\s+$/, ""))
142
+ .join("\n");
143
+ }
144
+ /**
145
+ * 字符条形:比例 → `█` 填充、`░` 补齐到 `barWidth` 显示列。比例超出 [0, 1] 时钳住。
146
+ *
147
+ * @param ratio 填充比例,0 到 1。
148
+ * @param barWidth 整条的显示宽度。
149
+ * @returns 定宽的字符条。
150
+ */
151
+ export function textBar(ratio, barWidth) {
152
+ const clamped = Math.max(0, Math.min(1, ratio));
153
+ const filled = Math.round(clamped * barWidth);
154
+ return "█".repeat(filled) + "░".repeat(barWidth - filled);
155
+ }
156
+ /**
157
+ * 多块文本并排成栏(`Row` 的 text 面):逐行拼接,各栏按显示宽度补齐,短栏补空行。
158
+ *
159
+ * @param blocks 每栏一块多行文本。
160
+ * @param columnWidths 每栏的显示宽度,与 `blocks` 一一对应。
161
+ * @param separator 栏间分隔符,默认 `" │ "`。
162
+ * @returns 并排后的文本块。
163
+ */
164
+ export function joinColumns(blocks, columnWidths, separator = " │ ") {
165
+ const columns = blocks.map((block) => block.split("\n"));
166
+ const height = Math.max(...columns.map((lines) => lines.length), 0);
167
+ const out = [];
168
+ for (let i = 0; i < height; i++) {
169
+ const parts = columns.map((lines, c) => padDisplay(lines[i] ?? "", columnWidths[c]));
170
+ out.push(parts.join(separator).replace(/\s+$/, ""));
171
+ }
172
+ return out.join("\n");
173
+ }
@@ -0,0 +1,37 @@
1
+ export interface PlotPoint {
2
+ /** 图上的标记字符(点或系列的字母)。 */
3
+ mark: string;
4
+ x: number;
5
+ y: number;
6
+ }
7
+ export interface CharPlotOptions {
8
+ /** 总列宽(含 y 轴刻度区)。 */
9
+ width: number;
10
+ /** 网格行数;默认 9。 */
11
+ height?: number;
12
+ points: PlotPoint[];
13
+ /** 值空间的折线(同系列点按 x 排序连线),用 · 描画,字母覆盖其上。 */
14
+ lines?: {
15
+ x: number;
16
+ y: number;
17
+ }[][];
18
+ xLabel: string;
19
+ yLabel: string;
20
+ /** 值 → 刻度文案(已格式化的 display)。 */
21
+ formatX: (value: number) => string;
22
+ formatY: (value: number) => string;
23
+ /** better: "lower" 的轴反向 —— 好的一端在右 / 上。 */
24
+ invertX?: boolean;
25
+ invertY?: boolean;
26
+ }
27
+ export declare function renderCharPlot(opts: CharPlotOptions): string;
28
+ /** 点太密排不下时的降级:坐标表,不硬挤(每行 key、x、y 的 display)。 */
29
+ export declare function renderCoordinateTable(rows: {
30
+ key: string;
31
+ x: string;
32
+ y: string;
33
+ }[], header: {
34
+ key: string;
35
+ x: string;
36
+ y: string;
37
+ }): string;
@@ -0,0 +1,81 @@
1
+ // text 面的字符坐标图:MetricScatter / MetricLine 共用。
2
+ // 形态照 docs-site/zh/guides/report-components.mdx 的示例块:y 轴刻度 + │ 边框,
3
+ // 点用字母标注、图例列在图下;lower-better 的轴反向,「好」的角落恒在右上。
4
+ import { padDisplay, padStartDisplay, stringWidth } from "./layout.js";
5
+ function makeScale(values, cells, invert) {
6
+ const lo = Math.min(...values);
7
+ const hi = Math.max(...values);
8
+ const span = hi - lo;
9
+ return {
10
+ lo,
11
+ hi,
12
+ at(value) {
13
+ // 单值域:唯一的点落正中
14
+ let t = span === 0 ? 0.5 : (value - lo) / span;
15
+ if (invert)
16
+ t = 1 - t;
17
+ return Math.max(0, Math.min(cells - 1, Math.round(t * (cells - 1))));
18
+ },
19
+ };
20
+ }
21
+ /** 值空间折线 → 网格 · 描画:相邻点之间按列逐格线性插值。 */
22
+ function drawLine(grid, a, b) {
23
+ const steps = Math.max(Math.abs(b.col - a.col), Math.abs(b.row - a.row));
24
+ for (let i = 1; i < steps; i++) {
25
+ const col = Math.round(a.col + ((b.col - a.col) * i) / steps);
26
+ const row = Math.round(a.row + ((b.row - a.row) * i) / steps);
27
+ if (grid[row][col] === " ")
28
+ grid[row][col] = "·";
29
+ }
30
+ }
31
+ export function renderCharPlot(opts) {
32
+ const height = Math.max(4, opts.height ?? 9);
33
+ const yTickTexts = [opts.formatY(Math.min(...opts.points.map((p) => p.y))), opts.formatY(Math.max(...opts.points.map((p) => p.y)))];
34
+ const gutter = Math.max(...yTickTexts.map(stringWidth)) + 1;
35
+ const plotWidth = Math.max(16, opts.width - gutter - 1);
36
+ const xScale = makeScale(opts.points.map((p) => p.x), plotWidth, opts.invertX ?? false);
37
+ // y:大下标 = 「好」的一端;网格第 0 行是顶端,行下标 = height-1 - at,
38
+ // 所以 invertY=false(higher 好)时 hi 折到 at=height-1 → 顶行,lower 好时反之
39
+ const yScale = makeScale(opts.points.map((p) => p.y), height, opts.invertY ?? false);
40
+ const rowOf = (y) => height - 1 - yScale.at(y);
41
+ const grid = Array.from({ length: height }, () => Array.from({ length: plotWidth }, () => " "));
42
+ for (const line of opts.lines ?? []) {
43
+ for (let i = 1; i < line.length; i++) {
44
+ drawLine(grid, { col: xScale.at(line[i - 1].x), row: rowOf(line[i - 1].y) }, { col: xScale.at(line[i].x), row: rowOf(line[i].y) });
45
+ }
46
+ }
47
+ for (const point of opts.points) {
48
+ grid[rowOf(point.y)][xScale.at(point.x)] = point.mark;
49
+ }
50
+ // y 轴刻度:极值行标注 display,其余空
51
+ const tickByRow = new Map();
52
+ tickByRow.set(rowOf(yScale.lo), opts.formatY(yScale.lo));
53
+ tickByRow.set(rowOf(yScale.hi), opts.formatY(yScale.hi));
54
+ const out = [];
55
+ out.push(`${" ".repeat(gutter)}${opts.yLabel} ↑`);
56
+ for (let row = 0; row < height; row++) {
57
+ const tick = tickByRow.get(row) ?? "";
58
+ out.push(`${padStartDisplay(tick, gutter)}│${grid[row].join("")}`.replace(/\s+$/, ""));
59
+ }
60
+ out.push(`${" ".repeat(gutter)}└${"─".repeat(plotWidth)}→ ${opts.xLabel}`);
61
+ // x 轴刻度:两端极值(反向轴时右端是 lo),标在真实位置上
62
+ const ticksRow = Array.from({ length: plotWidth }, () => " ");
63
+ for (const value of xScale.lo === xScale.hi ? [xScale.lo] : [xScale.lo, xScale.hi]) {
64
+ const label = opts.formatX(value);
65
+ let col = xScale.at(value);
66
+ col = Math.min(col, plotWidth - stringWidth(label));
67
+ for (let i = 0; i < label.length && col + i < plotWidth; i++)
68
+ ticksRow[col + i] = label[i];
69
+ }
70
+ out.push(`${" ".repeat(gutter + 1)}${ticksRow.join("")}`.replace(/\s+$/, ""));
71
+ return out.join("\n");
72
+ }
73
+ /** 点太密排不下时的降级:坐标表,不硬挤(每行 key、x、y 的 display)。 */
74
+ export function renderCoordinateTable(rows, header) {
75
+ const all = [header, ...rows];
76
+ const w1 = Math.max(...all.map((r) => stringWidth(r.key)));
77
+ const w2 = Math.max(...all.map((r) => stringWidth(r.x)));
78
+ return all
79
+ .map((r) => `${padDisplay(r.key, w1)} ${padDisplay(r.x, w2)} ${r.y}`.replace(/\s+$/, ""))
80
+ .join("\n");
81
+ }
@@ -0,0 +1,8 @@
1
+ import type { TableProps } from "../primitives.tsx";
2
+ import type { TextContext } from "../tree.ts";
3
+ /**
4
+ * <Table> 的 text 面:columns × rows → 对齐的字符表。
5
+ * 有任一行带 locator 时追加一列 attempt(locator 本身就是 `niceeval show <locator>`
6
+ * 的位置参数;逐行重复整条命令会把表撑宽,与三个实体列表 text 面的既有取舍一致)。
7
+ */
8
+ export declare function renderTableText(props: TableProps, ctx: TextContext): string;
@@ -0,0 +1,86 @@
1
+ // <Table> 原语的 text 面(../primitives.tsx 的 Table.text 就是它)。
2
+ // 官方的表状组件(MetricTable / MetricMatrix / Scoreboard / DeltaTable)的 text 面也直接
3
+ // 调它 —— 官方与自定义表共用同一个渲染器,是「用户组件与官方组件对等」的构造证明:
4
+ // 官方用不上的能力用户就拿不到,官方绕过它手搓它就一定会长歪。
5
+ //
6
+ // 列宽按显示宽度算(CJK 记 2 列);null 渲染 —,不补 0;超宽先折最宽的左对齐列,
7
+ // 压到下限仍放不下就从右侧丢列并如实报数(「截断报剩余」是既有契约,不在这里破例)。
8
+ import { countText, localeText } from "../locale.js";
9
+ import { renderAlignedRows, stringWidth, wrapDisplay } from "./layout.js";
10
+ const MISSING_MARK = "—";
11
+ /** 列间距,与 renderAlignedRows 的 join(" ") 一致。 */
12
+ const COLUMN_GAP = 3;
13
+ /** 左对齐列的压缩下限:再窄就读不成句,宁可丢列。 */
14
+ const MIN_TEXT_COLUMN = 8;
15
+ /** 格子的文本形态:缺这个键、或值是 null,都渲染 —(与 web 面同源)。 */
16
+ function cellText(row, key) {
17
+ const value = row.cells[key];
18
+ return value === null || value === undefined ? MISSING_MARK : value;
19
+ }
20
+ function totalWidth(widths) {
21
+ return widths.reduce((sum, w) => sum + w, 0) + COLUMN_GAP * Math.max(0, widths.length - 1);
22
+ }
23
+ /**
24
+ * 自然列宽 → 放得进 available 的列宽。两步,顺序即优先级:
25
+ * 1. 压最宽的左对齐列(文本列)到下限 —— 右对齐列是数字,折行读不了,不压;
26
+ * 2. 仍放不下就从右侧丢列(至少留一列),丢了几列如实返回。
27
+ */
28
+ function fitWidths(natural, align, available) {
29
+ const widths = [...natural];
30
+ while (totalWidth(widths) > available) {
31
+ let widest = -1;
32
+ for (let c = 0; c < widths.length; c++) {
33
+ if (align[c] === "right" || widths[c] <= MIN_TEXT_COLUMN)
34
+ continue;
35
+ if (widest === -1 || widths[c] > widths[widest])
36
+ widest = c;
37
+ }
38
+ if (widest === -1)
39
+ break;
40
+ const over = totalWidth(widths) - available;
41
+ widths[widest] = Math.max(MIN_TEXT_COLUMN, widths[widest] - over);
42
+ }
43
+ let hidden = 0;
44
+ while (widths.length > 1 && totalWidth(widths) > available) {
45
+ widths.pop();
46
+ hidden += 1;
47
+ }
48
+ return { widths, hidden };
49
+ }
50
+ /** 逻辑行 → 物理行:每格折到自己的列宽,行高取最高的那格,矮格补空串。 */
51
+ function toPhysicalRows(cells, widths) {
52
+ const wrapped = cells.map((cell, c) =>
53
+ // 放得下就原样保留:wrapDisplay 会把连续空格折成一个,而 DeltaTable 的
54
+ // "50% → 62% +12pp" 这类格子内含固定间距 —— 不折行时一个字节都不能动。
55
+ !cell.includes("\n") && stringWidth(cell) <= widths[c] ? [cell] : wrapDisplay(cell, widths[c]));
56
+ const height = Math.max(...wrapped.map((lines) => lines.length), 1);
57
+ const out = [];
58
+ for (let i = 0; i < height; i++)
59
+ out.push(wrapped.map((lines) => lines[i] ?? ""));
60
+ return out;
61
+ }
62
+ /**
63
+ * <Table> 的 text 面:columns × rows → 对齐的字符表。
64
+ * 有任一行带 locator 时追加一列 attempt(locator 本身就是 `niceeval show <locator>`
65
+ * 的位置参数;逐行重复整条命令会把表撑宽,与三个实体列表 text 面的既有取舍一致)。
66
+ */
67
+ export function renderTableText(props, ctx) {
68
+ const locale = props.locale ?? ctx.locale;
69
+ const hasLocator = props.rows.some((row) => row.locator !== undefined);
70
+ if (props.columns.length === 0 && !hasLocator)
71
+ return "";
72
+ const header = props.columns.map((column) => column.header);
73
+ const align = props.columns.map((column) => column.align ?? "left");
74
+ const body = props.rows.map((row) => props.columns.map((column) => cellText(row, column.key)));
75
+ if (hasLocator) {
76
+ header.push(localeText(locale, "table.attempt"));
77
+ align.push("left");
78
+ props.rows.forEach((row, i) => body[i].push(row.locator ?? MISSING_MARK));
79
+ }
80
+ const matrix = [header, ...body];
81
+ const natural = header.map((_, c) => Math.max(...matrix.map((row) => stringWidth(row[c] ?? ""))));
82
+ const { widths, hidden } = fitWidths(natural, align, ctx.width);
83
+ const physical = matrix.flatMap((row) => toPhysicalRows(row.slice(0, widths.length), widths));
84
+ const table = renderAlignedRows(physical, align);
85
+ return hidden > 0 ? `${table}\n${countText(locale, "table.columnsHidden", hidden)}` : table;
86
+ }
@@ -0,0 +1,99 @@
1
+ import type { ReactNode } from "react";
2
+ import type { AttemptLocator } from "../results/locator.ts";
3
+ import { type ReportLocale } from "./locale.ts";
4
+ /** 标准 jsx-runtime 元素形状;text 宿主只认 type / props,不管 $$typeof。 */
5
+ export interface ReportElement {
6
+ type: unknown;
7
+ props: Record<string, unknown>;
8
+ key?: unknown;
9
+ }
10
+ /** 报告树节点:元素、文本、数组 / Fragment 的儿子们,或渲染为空的空值。 */
11
+ export type ReportNode = ReportElement | string | number | boolean | null | undefined | ReportNode[];
12
+ /** 挂 faces 的私有键:text 宿主与树校验靠它识别双面组件。 */
13
+ export declare const COMPONENT_FACES: unique symbol;
14
+ export interface TextContext {
15
+ /** 可用列宽;Row 分栏后变窄。 */
16
+ width: number;
17
+ /** chrome 文案的 locale(verdict 词、注脚、占位符);默认 "en",show 输出不变。 */
18
+ locale: ReportLocale;
19
+ /** 容器组件渲染 children 用,宽度显式传递。 */
20
+ render(node: ReportNode, width?: number): string;
21
+ /** 下钻命令,通证据室:`niceeval show @<locator>`。 */
22
+ attemptCommand(locator: AttemptLocator): string;
23
+ }
24
+ export interface WebContext {
25
+ /** 证据室深链,同 view 的 attempt 路由(`#/attempt/@<locator>`,单段、不透明)。 */
26
+ attemptHref(locator: AttemptLocator): string;
27
+ /** chrome 文案的 locale;官方组件渲染面经上下文读取,宿主外默认 "en"。 */
28
+ locale: ReportLocale;
29
+ }
30
+ export interface ComponentFaces<P, R = P> {
31
+ /**
32
+ * 可选的数据解析面:把声明式 props(selection + 计算选项)在渲染前解析成纯数据 props(R)。
33
+ * 宿主的 {@link resolveReportTree} 在 build 之后、validate / render 之前调用它——这是整条
34
+ * 管线里唯一允许 await / IO 的组件级步骤;渲染面(web / text)只看已解析的 R,保持同步、零 IO。
35
+ * 不实现 resolve 时 R = P,组件被当作纯数据组件直接渲染。
36
+ */
37
+ resolve?(props: P): Promise<R>;
38
+ /** 真 React JSX 在这个面里;返回静态可渲染的 ReactNode。只看已解析的 R。 */
39
+ web(props: R, ctx: WebContext): ReactNode;
40
+ text(props: R, ctx: TextContext): string;
41
+ }
42
+ /**
43
+ * 双面组件的产物:可直接用于 JSX(React 把它当函数组件调用,走 web 面),
44
+ * text 宿主经 COMPONENT_FACES 调 text 面。R(解析后 props 形态)在存储边界抹成 any——
45
+ * 树遍历只按 ComponentFaces 的结构调用 resolve / web / text,不需要在类型层追踪 R。
46
+ */
47
+ export type ReportComponent<P> = ((props: P) => ReactNode) & {
48
+ [COMPONENT_FACES]: ComponentFaces<P, any>;
49
+ displayName?: string;
50
+ };
51
+ /** web 宿主用:在给定 WebContext 下同步渲染(React 静态渲染本身是同步的)。 */
52
+ export declare function runWithWebContext<T>(ctx: WebContext, fn: () => T): T;
53
+ /** 官方组件的装配用:宿主上下文激活时才把 ctx.attemptHref 当默认下钻。 */
54
+ export declare function isHostWebContextActive(): boolean;
55
+ /**
56
+ * 定义一个双面组件:faces 两键必填 —— 少实现一个面编译不过,配对是结构义务。
57
+ * 基础实现不 import react;产物以可调用组件的形状兼容 React 渲染。
58
+ */
59
+ export declare function defineComponent<P, R = P>(faces: ComponentFaces<P, R>): ReportComponent<P>;
60
+ export declare function facesOf(type: unknown): ComponentFaces<unknown> | undefined;
61
+ /**
62
+ * 报告 build 之后、树校验与 text/web 渲染之前的解析遍历:把声明式数据组件(实现了
63
+ * `faces.resolve` 的双面组件,如 selection 形态的 MetricScatter)的 props
64
+ * 就地换成算好的数据形态 props(三个实体列表没有 `resolve` 面,不经这一步——它们的 `items`
65
+ * 由报告作者在 `build()` 里直接 `await .data(selection)` 备好)。计算发生在这里(唯一允许
66
+ * await 的组件级步骤,连同报告函数体自己的 `build()`),两个渲染面
67
+ * 之后都只看已解析的树、保持同步零 IO。遍历形状与 {@link validateReportTree} /
68
+ * {@link renderNodeToText} 一致:
69
+ *
70
+ * - 同层数组兄弟并行解析(`Promise.all`),保持原始顺序 / keys;
71
+ * - 双面组件带 resolve 的:调 `faces.resolve(props)` 换 props(有 children 再递归解析);
72
+ * - 双面组件无 resolve 的(Row / Col / Section / RunOverview…):只递归 children,自身 props
73
+ * 原样保留(title / className 等不能动);
74
+ * - 普通函数组件:同步调用展开,展开结果继续解析(与 validate / render 对函数组件的处理一致);
75
+ * - 字符串 intrinsic(<div>):原样返回,交给随后的 validateReportTree 报同一条错误。
76
+ *
77
+ * resolver 跑完后,树里已没有函数组件 / 未解析 props;validate / render 里对这两者的分支在
78
+ * 正常管线下是 no-op,但保留着——手搭树直接调 validate / render(不过 resolver)的低层用法仍需要。
79
+ */
80
+ export declare function resolveReportTree(node: ReportNode): Promise<ReportNode>;
81
+ /**
82
+ * 渲染前树校验:页面树里只放双面组件、排版原语与普通组合函数,字符串 intrinsic
83
+ * (<div>)报错、指名组件路径。这是运行时校验而非编译期(标准 JSX 下 TS 把一切
84
+ * JSX 表达式统一成 JSX.Element);两个宿主渲染前跑同一遍 —— 不做单侧宽容,否则
85
+ * 对着 view 写的页面到 show 才炸。校验只下钻 children(children 就是报告树);
86
+ * 普通函数组件调用展开(渲染面纯同步,重复调用无副作用)。
87
+ */
88
+ export declare function validateReportTree(node: ReportNode, path?: string[]): void;
89
+ export interface TextRenderOptions {
90
+ /** 终端可用列宽;默认 80。 */
91
+ width?: number;
92
+ /** 下钻命令的生成;宿主注入,默认 `niceeval show @<locator>`(真实可跑的 CLI 语法)。 */
93
+ attemptCommand?: (locator: AttemptLocator) => string;
94
+ /** chrome 文案的 locale;默认 "en"(`niceeval show` 现有输出不变)。 */
95
+ locale?: ReportLocale;
96
+ }
97
+ export declare function createTextContext(options?: TextRenderOptions): TextContext;
98
+ /** text 宿主的遍历渲染:双面组件走 text 面,普通函数调用展开,块之间以换行相接。 */
99
+ export declare function renderNodeToText(node: ReportNode, ctx: TextContext): string;