niceeval 0.6.0 → 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 (87) hide show
  1. package/dist/agents/types.d.ts +5 -1
  2. package/dist/report/built-ins/experiment-comparison.d.ts +1 -0
  3. package/dist/report/built-ins/experiment-comparison.js +13 -0
  4. package/dist/report/built-ins/index.d.ts +1 -1
  5. package/dist/report/built-ins/index.js +1 -1
  6. package/dist/report/components.d.ts +3 -1
  7. package/dist/report/components.js +1 -1
  8. package/dist/report/compute.js +15 -33
  9. package/dist/report/format.d.ts +0 -6
  10. package/dist/report/format.js +1 -18
  11. package/dist/report/index.d.ts +2 -2
  12. package/dist/report/index.js +1 -1
  13. package/dist/report/locale.d.ts +13 -2
  14. package/dist/report/locale.js +26 -3
  15. package/dist/report/metrics.js +1 -1
  16. package/dist/report/react/AttemptList.d.ts +2 -2
  17. package/dist/report/react/AttemptList.js +3 -4
  18. package/dist/report/react/ExperimentList.d.ts +3 -2
  19. package/dist/report/react/ExperimentList.js +59 -7
  20. package/dist/report/react/MetricScatter.js +1 -5
  21. package/dist/report/react/fixtures.js +9 -4
  22. package/dist/report/react/format.d.ts +1 -1
  23. package/dist/report/react/format.js +1 -1
  24. package/dist/report/react/index.d.ts +1 -1
  25. package/dist/report/text/faces.js +71 -33
  26. package/dist/report/types.d.ts +3 -15
  27. package/docs-site/zh/concepts/adapter.mdx +2 -2
  28. package/docs-site/zh/concepts/assert.mdx +11 -10
  29. package/docs-site/zh/concepts/evals.mdx +7 -6
  30. package/docs-site/zh/concepts/overview.mdx +1 -1
  31. package/docs-site/zh/guides/agent-feedback-loop.mdx +9 -7
  32. package/docs-site/zh/guides/fixtures.mdx +3 -1
  33. package/docs-site/zh/guides/official-adapters.mdx +1 -1
  34. package/docs-site/zh/guides/publish-report.mdx +1 -1
  35. package/docs-site/zh/guides/report-components.mdx +13 -13
  36. package/docs-site/zh/guides/results-data.mdx +28 -2
  37. package/docs-site/zh/guides/sandbox-agent.mdx +1 -0
  38. package/docs-site/zh/guides/sandbox-providers.mdx +1 -1
  39. package/docs-site/zh/guides/viewing-results.mdx +6 -5
  40. package/docs-site/zh/guides/write-send.mdx +2 -1
  41. package/docs-site/zh/reference/cli.mdx +3 -3
  42. package/docs-site/zh/reference/define-agent.mdx +3 -1
  43. package/docs-site/zh/reference/events.mdx +2 -2
  44. package/docs-site/zh/reference/expect.mdx +10 -5
  45. package/package.json +1 -1
  46. package/src/agents/types.ts +5 -1
  47. package/src/i18n/en.ts +1 -2
  48. package/src/i18n/zh-CN.ts +1 -1
  49. package/src/report/built-in-user-parity.test.tsx +44 -47
  50. package/src/report/built-ins/experiment-comparison.tsx +19 -0
  51. package/src/report/built-ins/index.ts +1 -1
  52. package/src/report/components.tsx +3 -1
  53. package/src/report/compute.ts +12 -36
  54. package/src/report/dual-render.test.tsx +33 -29
  55. package/src/report/format.ts +1 -16
  56. package/src/report/index.ts +1 -2
  57. package/src/report/locale.ts +26 -3
  58. package/src/report/metrics.ts +1 -1
  59. package/src/report/react/AttemptList.tsx +4 -6
  60. package/src/report/react/ExperimentList.tsx +167 -55
  61. package/src/report/react/MetricScatter.tsx +1 -11
  62. package/src/report/react/enhance.js +39 -0
  63. package/src/report/react/fixtures.ts +10 -4
  64. package/src/report/react/format.ts +1 -1
  65. package/src/report/react/index.tsx +0 -1
  66. package/src/report/react/render.test.tsx +24 -8
  67. package/src/report/react/styles.css +40 -13
  68. package/src/report/report.test.ts +2 -7
  69. package/src/report/text/faces.ts +76 -36
  70. package/src/report/types.ts +3 -15
  71. package/src/results/host-equivalence.test.ts +3 -3
  72. package/src/show/index.ts +8 -15
  73. package/src/show/render.ts +7 -204
  74. package/src/show/show.test.ts +22 -23
  75. package/src/view/data.test.ts +1 -1
  76. package/src/view/data.ts +6 -6
  77. package/src/view/index.ts +1 -1
  78. package/src/view/shared/types.ts +1 -1
  79. package/src/view/view-report.test.ts +5 -5
  80. package/dist/o11y/execution-tree.d.ts +0 -103
  81. package/dist/o11y/otlp/select.d.ts +0 -22
  82. package/dist/report/built-ins/cost-pass-rate-comparison.d.ts +0 -1
  83. package/dist/report/built-ins/cost-pass-rate-comparison.js +0 -17
  84. package/dist/results/annotated-source.d.ts +0 -61
  85. package/dist/results/attempt-evidence.d.ts +0 -69
  86. package/dist/results/attempt-source.d.ts +0 -15
  87. package/src/report/built-ins/cost-pass-rate-comparison.tsx +0 -23
@@ -224,7 +224,11 @@ export interface AgentContext {
224
224
  * 具体取值)是两个维度——这里只是「跑的是哪个实验」的稳定标识,不携带条件内容。
225
225
  */
226
226
  readonly experimentId?: string;
227
- /** 仅沙箱型 agent 有(运行器按 --sandbox 备好)。 */
227
+ /**
228
+ * 所有 agent 都有:沙箱型是运行器按项目/experiment 配置备好的真实沙箱句柄,remote 型是
229
+ * `createRemoteSandbox()` 产出的 stub(仅含 `workdir`/`sandboxId`/`otlpHost`/`stop` 等
230
+ * 元信息,其余方法调用即抛错)。
231
+ */
228
232
  readonly sandbox: Sandbox;
229
233
  readonly session: AgentSession;
230
234
  /**
@@ -0,0 +1 @@
1
+ export declare const ExperimentComparison: import("../report.ts").ReportDefinition;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // ExperimentComparison:niceeval view 裸跑时的内置 experiment 比较报告。
3
+ //
4
+ // 名字描述整份报告回答的问题,不绑定其中某一张图。报告由成本 × 通过率散点和固定列
5
+ // ExperimentList 组成;它是一份普通 ReportDefinition,没有 renderer 私有通道。
6
+ import { Col } from "../primitives.js";
7
+ import { ExperimentList, MetricScatter } from "../components.js";
8
+ import { costUSD, passRate } from "../metrics.js";
9
+ import { defineReport } from "../report.js";
10
+ export const ExperimentComparison = defineReport(async ({ selection }) => {
11
+ const experiments = await ExperimentList.data(selection);
12
+ return (_jsxs(Col, { children: [_jsx(MetricScatter, { selection: selection, points: "experiment", series: "agent", x: costUSD, y: passRate }), _jsx(ExperimentList, { items: experiments, filter: true })] }));
13
+ });
@@ -1 +1 @@
1
- export { CostPassRateComparison } from "./cost-pass-rate-comparison.tsx";
1
+ export { ExperimentComparison } from "./experiment-comparison.tsx";
@@ -1,2 +1,2 @@
1
1
  // 内置报告目录:一个文件一个按展示内容命名的普通 ReportDefinition,显式导出值,不建字符串 registry。
2
- export { CostPassRateComparison } from "./cost-pass-rate-comparison.js";
2
+ export { ExperimentComparison } from "./experiment-comparison.js";
@@ -24,6 +24,8 @@ export interface GroupSummaryProps {
24
24
  */
25
25
  export interface ExperimentListProps {
26
26
  items: ExperimentListItem[];
27
+ /** web 面在比较表前显示实验过滤框;text 面忽略。 */
28
+ filter?: boolean;
27
29
  /** chrome 文案 locale;省略时随宿主上下文(宿主外默认 "en")。 */
28
30
  locale?: ReportLocale;
29
31
  className?: string;
@@ -141,7 +143,7 @@ export declare const EvalList: ReportComponent<EvalListProps> & {
141
143
  };
142
144
  /**
143
145
  * Attempt 列表:实体列表的叶子层,每项一个 Attempt,固定展示判定、断言、error、Judge 评语
144
- * (assertions 的 detail/evidence)与证据引用(locator + 证据能力标记)。它不预设只看失败;
146
+ * (assertions 的 detail/evidence)与证据引用(locator)。它不预设只看失败;
145
147
  * 报告作者过滤 `AttemptListItem[]`、用 `.slice()` 限量,`total` 让渲染面如实报告剩余数量。
146
148
  */
147
149
  export declare const AttemptList: ReportComponent<AttemptListProps> & {
@@ -71,7 +71,7 @@ export const EvalList = Object.assign(defineComponent({
71
71
  EvalList.displayName = "EvalList";
72
72
  /**
73
73
  * Attempt 列表:实体列表的叶子层,每项一个 Attempt,固定展示判定、断言、error、Judge 评语
74
- * (assertions 的 detail/evidence)与证据引用(locator + 证据能力标记)。它不预设只看失败;
74
+ * (assertions 的 detail/evidence)与证据引用(locator)。它不预设只看失败;
75
75
  * 报告作者过滤 `AttemptListItem[]`、用 `.slice()` 限量,`total` 让渲染面如实报告剩余数量。
76
76
  */
77
77
  export const AttemptList = Object.assign(defineComponent({
@@ -174,33 +174,6 @@ export async function tableData(input, opts) {
174
174
  // 没有列配置;过滤是报告作者对返回数组调用 .filter()/.slice() 的事,不进这里
175
175
  // (docs/feature/reports/library.md「实体列表」)。AttemptListItem 是三者共用的叶子形状——
176
176
  // ExperimentList / EvalList 的下钻数组直接复用它,不各自精简一份。
177
- /**
178
- * 一个 attempt 的证据能力标记,只读已有的瘦身摘要位(`hasSources` / `hasEvents` / `hasTrace`)
179
- * 加一次 `diff()` 懒加载——不调用完整的 `loadAttemptEvidence`(那还会额外装配 Eval 源码标注
180
- * 与 ExecutionTree,这里只要四个布尔位)。三位定义与 `AttemptEvidence.capabilities` 完全一致:
181
- *
182
- * - `eval`:`hasSources` 为真。真正的门槛是 `AnnotatedEvalSource` 非空且有内容(源码行或断言
183
- * 非空),但 sources 存在时 `buildAnnotatedEvalSource` 恒产出至少一行源码,断言又永远来自
184
- * `result.assertions`(不需要 IO)——`hasSources` 是这个条件的精确瘦身版,不是近似。
185
- * - `execution`:`hasEvents` 为真,与 `loadAttemptEvidence` 的 `events !== null && events.length > 0`
186
- * 同一个判定,写入面按同一条规则算的 `hasEvents` 就是它的瘦身镜像。
187
- * - `timing`:`hasEvents && hasTrace` 为真——`execution` 成立且这次运行接入过 OTel
188
- * (`ExecutionTree.timingAvailable` = `spans.length > 0`,`hasTrace` 正是这个判定的瘦身镜像)。
189
- * - `diff`:唯一需要 IO 的一位,`attempt.diff()` 非空且至少改动/删除了一个文件——落盘没有
190
- * 与 `hasEvents` 对应的瘦身摘要位,只能懒加载判定;成本是每个 item 一次 `diff()` 读取,
191
- * 不是完整证据装配的四路 Promise.all。
192
- */
193
- async function attemptCapabilities(attempt) {
194
- const result = attempt.result;
195
- const diff = await attempt.diff();
196
- const diffCapable = diff !== null && (Object.keys(diff.generatedFiles).length > 0 || diff.deletedFiles.length > 0);
197
- return {
198
- eval: result.hasSources === true,
199
- execution: result.hasEvents === true,
200
- timing: result.hasEvents === true && result.hasTrace === true,
201
- diff: diffCapable,
202
- };
203
- }
204
177
  /** 自由文本(error / 断言 detail / evidence)的发布消毒钩子;身份字段(name/severity/loc)不经它。 */
205
178
  function redactAssertions(assertions, redact) {
206
179
  if (assertions.length === 0)
@@ -212,7 +185,7 @@ function redactAssertions(assertions, redact) {
212
185
  }));
213
186
  }
214
187
  /** AttemptList / ExperimentList / EvalList 共用的叶子构造:一个 Item → 一个 AttemptListItem。 */
215
- async function attemptListItemOf(item, redact) {
188
+ function attemptListItemOf(item, redact) {
216
189
  const result = item.attempt.result;
217
190
  const cost = attemptCostUSD(result);
218
191
  return {
@@ -226,7 +199,6 @@ async function attemptListItemOf(item, redact) {
226
199
  durationMs: result.durationMs,
227
200
  ...(cost !== null ? { costUSD: cost } : {}),
228
201
  locator: locatorOf(item),
229
- capabilities: await attemptCapabilities(item.attempt),
230
202
  };
231
203
  }
232
204
  const identityRedact = (text) => text;
@@ -235,7 +207,7 @@ export async function attemptListData(input, opts) {
235
207
  const { snapshots } = resolveInput(input);
236
208
  const redact = opts?.redact ?? identityRedact;
237
209
  const items = collectItems(snapshots);
238
- return Promise.all(items.map((item) => attemptListItemOf(item, redact)));
210
+ return items.map((item) => attemptListItemOf(item, redact));
239
211
  }
240
212
  /** `EvalList.data(selection)`:每个 `experimentId + evalId` 一项,按 evalId 再按 experimentId 升序。 */
241
213
  export async function evalListData(input) {
@@ -255,7 +227,7 @@ export async function evalListData(input) {
255
227
  const sorted = [...group].sort((a, b) => a.attempt.result.attempt - b.attempt.result.attempt);
256
228
  const verdict = foldEvalVerdict(sorted.map((item) => item.attempt.result));
257
229
  const representative = sorted.find((item) => item.attempt.result.verdict === verdict) ?? sorted[0];
258
- const attempts = await Promise.all(sorted.map((item) => attemptListItemOf(item, identityRedact)));
230
+ const attempts = sorted.map((item) => attemptListItemOf(item, identityRedact));
259
231
  out.push({
260
232
  evalId: evalIdOf(sorted[0]),
261
233
  experimentId: experimentIdOf(sorted[0]),
@@ -285,7 +257,7 @@ export async function experimentListData(input) {
285
257
  const sorted = [...evalItems].sort((a, b) => a.attempt.result.attempt - b.attempt.result.attempt);
286
258
  const verdict = foldEvalVerdict(sorted.map((item) => item.attempt.result));
287
259
  const representative = sorted.find((item) => item.attempt.result.verdict === verdict) ?? sorted[0];
288
- const attempts = await Promise.all(sorted.map((item) => attemptListItemOf(item, identityRedact)));
260
+ const attempts = sorted.map((item) => attemptListItemOf(item, identityRedact));
289
261
  evalRows.push({
290
262
  evalId,
291
263
  verdict,
@@ -315,7 +287,17 @@ export async function experimentListData(input) {
315
287
  evalRows,
316
288
  });
317
289
  }
318
- out.sort((a, b) => a.experimentId.localeCompare(b.experimentId));
290
+ // ExperimentList 是默认实验比较表:初始态按成功率从高到低,缺数据沉底;
291
+ // 同分时按 experiment id 稳定排序。web 增强可临时重排,text 面沿用同一基准顺序。
292
+ out.sort((a, b) => {
293
+ if (a.passRate.value === null && b.passRate.value === null)
294
+ return a.experimentId.localeCompare(b.experimentId);
295
+ if (a.passRate.value === null)
296
+ return 1;
297
+ if (b.passRate.value === null)
298
+ return -1;
299
+ return b.passRate.value - a.passRate.value || a.experimentId.localeCompare(b.experimentId);
300
+ });
319
301
  return out;
320
302
  }
321
303
  export async function matrixData(input, opts) {
@@ -1,4 +1,3 @@
1
- import type { AttemptEvidenceCapabilities } from "../results/attempt-evidence.ts";
2
1
  import type { AssertionResult, Verdict } from "../types.ts";
3
2
  export declare function formatMetricValue(value: number, unit?: string): string;
4
3
  /** 无单位纯数字(scoreboard 总分等):一位小数,去尾零。 */
@@ -13,11 +12,6 @@ export declare function formatUSD(usd: number): string;
13
12
  export declare function formatPercent(ratio: number): string;
14
13
  /** passed / failed / errored / skipped 的判定符。 */
15
14
  export declare function verdictMark(verdict: Verdict): string;
16
- /**
17
- * 证据能力标记:`[E,X,⏱]` 式方括号列表,固定顺序 eval → execution → timing → diff,
18
- * 缺哪个不列哪个;四位全无时返回空串(没有证据可看,不画空 `[]`)。
19
- */
20
- export declare function capabilityBadge(capabilities: AttemptEvidenceCapabilities): string;
21
15
  /**
22
16
  * 一个 AttemptListItem 的失败原因摘要,按优先级取第一个在场的:`error` → 未通过的 gate 断言
23
17
  * (原始声明顺序,`name`,detail 在场则 `"name: detail"`,多条用「, 」连接)→ 缺席。与
@@ -77,8 +77,7 @@ export function formatUSD(usd) {
77
77
  export function formatPercent(ratio) {
78
78
  return `${Math.round(ratio * 100)}%`;
79
79
  }
80
- // ── 实体列表(ExperimentList / EvalList / AttemptList)共用的判定符与证据能力位 ──
81
- // web / text 两面对同一个 AttemptListItem 给出同一个标记(docs-site「终端输出形成反馈闭环」)。
80
+ // ── 实体列表(ExperimentList / EvalList / AttemptList)共用的判定符 ──
82
81
  /** passed / failed / errored / skipped 的判定符。 */
83
82
  export function verdictMark(verdict) {
84
83
  switch (verdict) {
@@ -92,22 +91,6 @@ export function verdictMark(verdict) {
92
91
  return "–";
93
92
  }
94
93
  }
95
- /**
96
- * 证据能力标记:`[E,X,⏱]` 式方括号列表,固定顺序 eval → execution → timing → diff,
97
- * 缺哪个不列哪个;四位全无时返回空串(没有证据可看,不画空 `[]`)。
98
- */
99
- export function capabilityBadge(capabilities) {
100
- const marks = [];
101
- if (capabilities.eval)
102
- marks.push("E");
103
- if (capabilities.execution)
104
- marks.push("X");
105
- if (capabilities.timing)
106
- marks.push("⏱");
107
- if (capabilities.diff)
108
- marks.push("D");
109
- return marks.length > 0 ? `[${marks.join(",")}]` : "";
110
- }
111
94
  /**
112
95
  * 一个 AttemptListItem 的失败原因摘要,按优先级取第一个在场的:`error` → 未通过的 gate 断言
113
96
  * (原始声明顺序,`name`,detail 在场则 `"name: detail"`,多条用「, 」连接)→ 缺席。与
@@ -8,12 +8,12 @@ export { Row, Col, Section, Text, Style, Table } from "./primitives.tsx";
8
8
  export type { LayoutProps, SectionProps, StyleProps, TableColumn, TableProps, TableRow } from "./primitives.tsx";
9
9
  export { stringWidth, padDisplay as padEnd, padStartDisplay as padStart, wrapDisplay as wrapText, indentBlock as indent, textBar as bar, joinColumns as columns, } from "./text/layout.ts";
10
10
  export type { ColumnAlign } from "./text/layout.ts";
11
- export { CostPassRateComparison } from "./built-ins/index.ts";
11
+ export { ExperimentComparison } from "./built-ins/index.ts";
12
12
  export { DEFAULT_REPORT_LOCALE, resolveMetricLabel } from "./locale.ts";
13
13
  export type { LocalizedLabel, ReportLocale } from "./locale.ts";
14
14
  export { AttemptList, DeltaTable, EvalList, ExperimentList, GroupSummary, MetricBars, MetricLine, MetricMatrix, MetricScatter, MetricTable, RunOverview, Scoreboard, } from "./components.tsx";
15
15
  export type { AttemptListProps, DeltaTableProps, EvalListProps, ExperimentListProps, GroupSummaryProps, MetricLineProps, MetricMatrixProps, MetricScatterProps, MetricTableProps, RunOverviewProps, ScoreboardProps, } from "./components.tsx";
16
16
  export type { AttemptListDataOptions, DeltaDataOptions, DeltaPair, LineDataOptions, MatrixDataOptions, ScatterDataOptions, ScoreboardDataOptions, TableDataOptions, } from "./compute.ts";
17
- export type { Aggregator, AttemptEvidenceCapabilities, AttemptListItem, AttemptLocator, DeltaData, Dimension, DimensionInput, EvalListItem, ExperimentListEvalRow, ExperimentListItem, GroupSummaryData, LineAxis, LineData, MatrixData, Metric, MetricAggregate, MetricCell, MetricColumn, OverviewData, FlagRef, ScatterData, ScoreboardData, SelectionWarning, TableData, TableRowMeta, } from "./types.ts";
17
+ export type { Aggregator, AttemptListItem, AttemptLocator, DeltaData, Dimension, DimensionInput, EvalListItem, ExperimentListEvalRow, ExperimentListItem, GroupSummaryData, LineAxis, LineData, MatrixData, Metric, MetricAggregate, MetricCell, MetricColumn, OverviewData, FlagRef, ScatterData, ScoreboardData, SelectionWarning, TableData, TableRowMeta, } from "./types.ts";
18
18
  export type { AttemptHandle, Results, Selection, Snapshot } from "../results/types.ts";
19
19
  export { experimentGroupOf } from "../shared/aggregate.ts";
@@ -20,7 +20,7 @@ export { Row, Col, Section, Text, Style, Table } from "./primitives.js";
20
20
  // 它数 UTF-16 码元不数显示列宽,一带中文列就撕歪。renderAlignedRows 刻意不导出。
21
21
  export { stringWidth, padDisplay as padEnd, padStartDisplay as padStart, wrapDisplay as wrapText, indentBlock as indent, textBar as bar, joinColumns as columns, } from "./text/layout.js";
22
22
  // 内置报告(show / view 裸跑时报告槽的出厂填充,一份普通 ReportDefinition,无 renderer 特权)
23
- export { CostPassRateComparison } from "./built-ins/index.js";
23
+ export { ExperimentComparison } from "./built-ins/index.js";
24
24
  // locale:官方组件 chrome 文案的语言(en / zh-CN);指标 label 可按 locale 给字典
25
25
  export { DEFAULT_REPORT_LOCALE, resolveMetricLabel } from "./locale.js";
26
26
  // 官方双面组件(各自带 data 计算函数;MetricBars.data = MetricMatrix.data)
@@ -22,6 +22,19 @@ declare const en: {
22
22
  readonly "table.viewBreakdown": "Per-eval breakdown";
23
23
  /** <Table> 的 locator 列表头(行带 locator 时自动追加)。 */
24
24
  readonly "table.attempt": "attempt";
25
+ readonly "experimentList.experiment": "Experiment";
26
+ readonly "experimentList.avgDuration": "Avg duration";
27
+ readonly "experimentList.passRate": "Pass rate";
28
+ readonly "experimentList.tokens": "Tokens";
29
+ readonly "experimentList.estimatedCost": "Est. cost";
30
+ readonly "experimentList.result": "Result";
31
+ readonly "experimentList.status": "Status";
32
+ readonly "experimentList.evalAttempt": "Eval / Attempt";
33
+ readonly "experimentList.duration": "Duration";
34
+ readonly "experimentList.cost": "Cost";
35
+ readonly "experimentList.filterPlaceholder": "Filter experiments…";
36
+ readonly "experimentList.defaultModel": "default";
37
+ readonly "experimentList.flags": "Flags";
25
38
  /** <Table> 压到下限仍放不下时,从右侧丢列并如实报数。 */
26
39
  readonly "table.columnsHidden.one": "({n} more column not shown)";
27
40
  readonly "table.columnsHidden.other": "({n} more columns not shown)";
@@ -46,8 +59,6 @@ declare const en: {
46
59
  readonly "scatter.axisReversed": "(axis reversed: right = better)";
47
60
  /** 0 个可画点:x/y 指标没有可用数据。 */
48
61
  readonly "scatter.noData": "No data to plot {x} × {y}";
49
- /** 恰好 1 个可画点:比较至少要两个实验。 */
50
- readonly "scatter.needTwo": "At least 2 experiments needed to compare {x} × {y}";
51
62
  readonly "pointsMissing.one": "{n} point missing data";
52
63
  readonly "pointsMissing.other": "{n} points missing data";
53
64
  readonly "attemptList.empty": "No attempts";
@@ -25,6 +25,19 @@ const en = {
25
25
  "table.viewBreakdown": "Per-eval breakdown",
26
26
  /** <Table> 的 locator 列表头(行带 locator 时自动追加)。 */
27
27
  "table.attempt": "attempt",
28
+ "experimentList.experiment": "Experiment",
29
+ "experimentList.avgDuration": "Avg duration",
30
+ "experimentList.passRate": "Pass rate",
31
+ "experimentList.tokens": "Tokens",
32
+ "experimentList.estimatedCost": "Est. cost",
33
+ "experimentList.result": "Result",
34
+ "experimentList.status": "Status",
35
+ "experimentList.evalAttempt": "Eval / Attempt",
36
+ "experimentList.duration": "Duration",
37
+ "experimentList.cost": "Cost",
38
+ "experimentList.filterPlaceholder": "Filter experiments…",
39
+ "experimentList.defaultModel": "default",
40
+ "experimentList.flags": "Flags",
28
41
  /** <Table> 压到下限仍放不下时,从右侧丢列并如实报数。 */
29
42
  "table.columnsHidden.one": "({n} more column not shown)",
30
43
  "table.columnsHidden.other": "({n} more columns not shown)",
@@ -49,8 +62,6 @@ const en = {
49
62
  "scatter.axisReversed": "(axis reversed: right = better)",
50
63
  /** 0 个可画点:x/y 指标没有可用数据。 */
51
64
  "scatter.noData": "No data to plot {x} × {y}",
52
- /** 恰好 1 个可画点:比较至少要两个实验。 */
53
- "scatter.needTwo": "At least 2 experiments needed to compare {x} × {y}",
54
65
  "pointsMissing.one": "{n} point missing data",
55
66
  "pointsMissing.other": "{n} points missing data",
56
67
  "attemptList.empty": "No attempts",
@@ -90,6 +101,19 @@ const zhCN = {
90
101
  "table.reason": "原因",
91
102
  "table.viewBreakdown": "逐题明细",
92
103
  "table.attempt": "Attempt",
104
+ "experimentList.experiment": "实验",
105
+ "experimentList.avgDuration": "平均耗时",
106
+ "experimentList.passRate": "成功率",
107
+ "experimentList.tokens": "Tokens",
108
+ "experimentList.estimatedCost": "预估成本",
109
+ "experimentList.result": "结果",
110
+ "experimentList.status": "状态",
111
+ "experimentList.evalAttempt": "题目 / Attempt",
112
+ "experimentList.duration": "耗时",
113
+ "experimentList.cost": "成本",
114
+ "experimentList.filterPlaceholder": "筛选实验…",
115
+ "experimentList.defaultModel": "默认",
116
+ "experimentList.flags": "Flags",
93
117
  "table.columnsHidden.one": "(还有 {n} 列未列出)",
94
118
  "table.columnsHidden.other": "(还有 {n} 列未列出)",
95
119
  "overview.snapshots": "快照",
@@ -111,7 +135,6 @@ const zhCN = {
111
135
  "scatter.betterUpperRight": "更好 → 右上",
112
136
  "scatter.axisReversed": "(轴反向:右 = 更好)",
113
137
  "scatter.noData": "{x} × {y} 没有可绘制的数据",
114
- "scatter.needTwo": "至少需要 2 个实验才能比较 {x} × {y}",
115
138
  "pointsMissing.one": "{n} 个点缺数据",
116
139
  "pointsMissing.other": "{n} 个点缺数据",
117
140
  "attemptList.empty": "没有 attempt",
@@ -13,7 +13,7 @@
13
13
  //
14
14
  // 两档指标(docs/feature/reports/library.md「内置指标」):以上除 turns 外全部只读 attempt.result
15
15
  // 的瘦身字段——任何 producer、任何 copySnapshots artifacts 选择都算得出,内置报告
16
- // CostPassRateComparison 只用这一档。turns 读 attempt.o11y()(懒加载 artifact),发布时若
16
+ // ExperimentComparison 只用这一档。turns 读 attempt.o11y()(懒加载 artifact),发布时若
17
17
  // o11y 没随行就诚实渲染缺数据「—」,不算 0——报告作者自己摆时心里要有这根弦,内置报告不用它。
18
18
  /**
19
19
  * 定义一个指标。内置指标与自定义指标是同一个类型,没有特权;
@@ -2,9 +2,9 @@ import type { ReactElement } from "react";
2
2
  import type { AttemptListItem } from "../types.ts";
3
3
  import type { AttemptLocator } from "../../results/locator.ts";
4
4
  import { type ReportLocale } from "../locale.ts";
5
- /** locator + 判定符 + 证据能力标记的普通 <a>,AttemptList/EvalList/ExperimentList 共用。 */
5
+ /** locator + 判定符的普通 <a>,AttemptList/EvalList/ExperimentList 共用。 */
6
6
  export declare function AttemptLocatorBadge({ item, attemptHref, }: {
7
- item: Pick<AttemptListItem, "locator" | "verdict" | "capabilities">;
7
+ item: Pick<AttemptListItem, "locator" | "verdict">;
8
8
  attemptHref: (locator: AttemptLocator) => string;
9
9
  }): ReactElement;
10
10
  /** 一条 Attempt 的完整卡片:AttemptList 自己的一项,也是 EvalList 展开区里的一行。 */
@@ -1,11 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { DEFAULT_REPORT_LOCALE, localeText } from "../locale.js";
3
3
  import { colorClassForKey } from "./colors.js";
4
- import { capabilityBadge, cx, formatDurationMs, formatUSD, verdictMark } from "./format.js";
5
- /** locator + 判定符 + 证据能力标记的普通 <a>,AttemptList/EvalList/ExperimentList 共用。 */
4
+ import { cx, formatDurationMs, formatUSD, verdictMark } from "./format.js";
5
+ /** locator + 判定符的普通 <a>,AttemptList/EvalList/ExperimentList 共用。 */
6
6
  export function AttemptLocatorBadge({ item, attemptHref, }) {
7
- const caps = capabilityBadge(item.capabilities);
8
- return (_jsxs("a", { className: cx("nre-locator", `nre-verdict-${item.verdict}`), href: attemptHref(item.locator), children: [item.locator, _jsx("span", { className: "nre-locator-mark", children: verdictMark(item.verdict) }), caps && _jsx("span", { className: "nre-locator-caps", children: caps })] }));
7
+ return (_jsxs("a", { className: cx("nre-locator", `nre-verdict-${item.verdict}`), href: attemptHref(item.locator), children: [item.locator, _jsx("span", { className: "nre-locator-mark", children: verdictMark(item.verdict) })] }));
9
8
  }
10
9
  function AssertionRow({ assertion, locale }) {
11
10
  return (_jsxs("li", { className: cx("nre-assertion", `nre-assertion-${assertion.severity}`, !assertion.passed && "nre-assertion-failed"), children: [_jsx("span", { className: "nre-assertion-severity", children: assertion.severity }), _jsx("span", { className: "nre-assertion-name", children: assertion.name }), _jsx("span", { className: "nre-assertion-score", children: localeText(locale, "attemptList.score", { score: assertion.score }) }), (assertion.detail || assertion.evidence) && (_jsxs("details", { className: "nre-assertion-more", children: [_jsx("summary", { children: localeText(locale, "attemptList.details") }), assertion.detail && _jsx("p", { className: "nre-assertion-detail", children: assertion.detail }), assertion.evidence && _jsx("blockquote", { className: "nre-assertion-evidence", children: assertion.evidence })] }))] }));
@@ -1,10 +1,11 @@
1
1
  import type { ReactElement } from "react";
2
- import type { ExperimentListItem } from "../types.ts";
3
2
  import type { AttemptLocator } from "../../results/locator.ts";
3
+ import type { ExperimentListItem } from "../types.ts";
4
4
  import { type ReportLocale } from "../locale.ts";
5
- export declare function ExperimentList({ items, attemptHref, className, locale, }: {
5
+ export declare function ExperimentList({ items, attemptHref, filter, className, locale, }: {
6
6
  items: ExperimentListItem[];
7
7
  attemptHref: (locator: AttemptLocator) => string;
8
+ filter?: boolean;
8
9
  className?: string;
9
10
  locale?: ReportLocale;
10
11
  }): ReactElement;
@@ -1,15 +1,67 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { attemptItemReason } from "../format.js";
2
3
  import { DEFAULT_REPORT_LOCALE, localeText } from "../locale.js";
3
- import { MetricCellView } from "./cell.js";
4
4
  import { AttemptLocatorBadge } from "./AttemptList.js";
5
+ import { MetricCellView } from "./cell.js";
5
6
  import { colorClassForKey } from "./colors.js";
6
7
  import { cx, formatDurationMs, formatUSD, verdictMark } from "./format.js";
7
- function EvalRow({ row, attemptHref, }) {
8
- return (_jsxs("li", { className: cx("nre-experiment-eval-row", `nre-eval-${row.verdict}`), children: [_jsx("span", { className: cx("nre-eval-verdict", `nre-verdict-${row.verdict}`), children: verdictMark(row.verdict) }), _jsx("span", { className: "nre-eval-id", children: row.evalId }), _jsx("span", { className: "nre-eval-attempt-badges", children: row.attempts.map((attempt) => (_jsx(AttemptLocatorBadge, { item: attempt, attemptHref: attemptHref }, attempt.locator))) }), row.verdict === "passed" ? (_jsxs("span", { className: "nre-eval-avg", children: [formatDurationMs(row.duration.value ?? 0), row.cost.value !== null && _jsxs(_Fragment, { children: [" \u00B7 ", formatUSD(row.cost.value)] })] })) : (_jsx("span", { className: "nre-eval-reason", children: row.reason }))] }));
8
+ const verdictOrder = ["passed", "failed", "errored", "skipped"];
9
+ function passRateTone(value) {
10
+ if (value === null)
11
+ return undefined;
12
+ if (value >= 0.8)
13
+ return "nre-good";
14
+ if (value >= 0.5)
15
+ return "nre-warn";
16
+ return "nre-bad";
17
+ }
18
+ function formatDate(value, locale) {
19
+ const date = new Date(value);
20
+ if (Number.isNaN(date.valueOf()))
21
+ return value;
22
+ return new Intl.DateTimeFormat(locale === "zh-CN" ? "zh-CN" : "en", {
23
+ month: "short",
24
+ day: "numeric",
25
+ hour: "2-digit",
26
+ minute: "2-digit",
27
+ }).format(date);
28
+ }
29
+ function VerdictSummary({ item, locale }) {
30
+ const parts = verdictOrder
31
+ .filter((verdict) => item.verdicts[verdict] > 0)
32
+ .map((verdict) => `${item.verdicts[verdict]} ${localeText(locale, `verdict.${verdict}`)}`);
33
+ return _jsx("span", { className: "nre-experiment-pill", children: parts.join(" / ") || "—" });
34
+ }
35
+ function ExperimentAttemptRow({ attempt, last, attemptHref, }) {
36
+ const reason = attemptItemReason(attempt);
37
+ return (_jsxs("li", { className: cx("nre-experiment-attempt-row", `nre-eval-${attempt.verdict}`), children: [_jsx("span", { className: "nre-attempt-branch", "aria-hidden": "true", children: last ? "└─" : "├─" }), _jsx("span", { className: "nre-eval-attempt-badges", children: _jsx(AttemptLocatorBadge, { item: attempt, attemptHref: attemptHref }) }), _jsxs("span", { className: "nre-eval-attempt-metrics", children: [formatDurationMs(attempt.durationMs), attempt.costUSD !== undefined && _jsxs(_Fragment, { children: [" \u00B7 ", formatUSD(attempt.costUSD)] })] }), _jsx("span", { className: "nre-eval-reason", children: reason ?? "—" })] }));
38
+ }
39
+ function EvalAttempts({ row, attemptHref, locale, }) {
40
+ return (_jsxs("li", { className: "nre-experiment-eval", children: [_jsxs("div", { className: cx("nre-experiment-eval-header", `nre-eval-${row.verdict}`), children: [_jsx("span", { className: cx("nre-eval-verdict", `nre-verdict-${row.verdict}`), children: verdictMark(row.verdict) }), _jsx("span", { className: "nre-eval-id", children: row.evalId }), _jsx("span", { className: "nre-eval-attempt-count", children: localeText(locale, "overview.attemptsCount", { n: row.attempts.length }) }), _jsx("span", { className: "nre-eval-summary", children: row.verdict === "passed"
41
+ ? [formatDurationMs(row.duration.value ?? 0), row.cost.value === null ? undefined : formatUSD(row.cost.value)]
42
+ .filter((value) => value !== undefined)
43
+ .join(" · ")
44
+ : row.reason })] }), _jsx("ul", { className: "nre-experiment-attempts", children: row.attempts.map((attempt, index) => (_jsx(ExperimentAttemptRow, { attempt: attempt, last: index === row.attempts.length - 1, attemptHref: attemptHref }, attempt.locator))) })] }));
45
+ }
46
+ function Flags({ flags, locale }) {
47
+ if (!flags || Object.keys(flags).length === 0)
48
+ return null;
49
+ return (_jsxs("div", { className: "nre-experiment-flags", children: [_jsx("span", { children: localeText(locale, "experimentList.flags") }), Object.entries(flags).map(([key, value]) => (_jsxs("b", { children: [key, "=", String(value)] }, key)))] }));
9
50
  }
10
- function ExperimentCard({ item, attemptHref, locale, }) {
11
- return (_jsx("li", { className: "nre-experiment-entry", children: _jsxs("details", { className: "nre-experiment-details", children: [_jsxs("summary", { className: "nre-experiment-summary", children: [_jsx("span", { className: cx("nre-experiment-id", "nre-key", colorClassForKey(item.experimentId)), children: item.experimentId }), _jsx("span", { className: "nre-experiment-agent", children: item.agent }), item.model && _jsx("span", { className: "nre-experiment-model", children: item.model }), _jsx("span", { className: "nre-experiment-cell", children: _jsx(MetricCellView, { cell: item.passRate, attemptHref: attemptHref, locale: locale }) }), _jsx("span", { className: "nre-experiment-cell", children: _jsx(MetricCellView, { cell: item.cost, attemptHref: attemptHref, locale: locale }) }), _jsx("span", { className: "nre-experiment-cell", children: _jsx(MetricCellView, { cell: item.duration, attemptHref: attemptHref, locale: locale }) }), _jsxs("span", { className: "nre-experiment-meta-sub", children: [localeText(locale, "overview.evalsCount", { n: item.evals }), item.attempts > item.evals ? ` · ${localeText(locale, "overview.attemptsCount", { n: item.attempts })}` : "", ` · ${localeText(locale, "latestRun", { run: item.lastRunAt })}`] })] }), _jsx("ul", { className: "nre-experiment-evals", children: item.evalRows.map((row) => (_jsx(EvalRow, { row: row, attemptHref: attemptHref }, row.evalId))) })] }) }));
51
+ function ExperimentRow({ item, attemptHref, locale, }) {
52
+ return (_jsxs("details", { className: "nre-experiment-entry", children: [_jsxs("summary", { className: "nre-experiment-summary", children: [_jsxs("span", { className: "nre-experiment-name", "data-sort-value": item.experimentId, children: [_jsx("b", { className: cx("nre-experiment-id", "nre-key", colorClassForKey(item.experimentId)), children: item.experimentId }), _jsxs("small", { children: [localeText(locale, "overview.evalsCount", { n: item.evals }), item.attempts > item.evals ? ` · ${localeText(locale, "overview.attemptsCount", { n: item.attempts })}` : "", ` · ${formatDate(item.lastRunAt, locale)}`] })] }), _jsx("span", { "data-sort-value": item.model ?? "", children: item.model ?? localeText(locale, "experimentList.defaultModel") }), _jsx("span", { "data-sort-value": item.agent, children: item.agent }), _jsx("span", { className: "nre-num", "data-sort-value": item.duration.value ?? "", children: _jsx(MetricCellView, { cell: item.duration, locale: locale }) }), _jsx("span", { className: cx("nre-num", passRateTone(item.passRate.value)), "data-sort-value": item.passRate.value ?? "", children: _jsx(MetricCellView, { cell: item.passRate, locale: locale }) }), _jsx("span", { className: "nre-num", "data-sort-value": item.tokens.value ?? "", children: _jsx(MetricCellView, { cell: item.tokens, locale: locale }) }), _jsx("span", { className: "nre-num", "data-sort-value": item.cost.value ?? "", children: _jsx(MetricCellView, { cell: item.cost, locale: locale }) }), _jsx("span", { "data-sort-value": item.verdicts.passed, children: _jsx(VerdictSummary, { item: item, locale: locale }) })] }), _jsxs("div", { className: "nre-experiment-detail", children: [_jsx(Flags, { flags: item.flags, locale: locale }), _jsx("ul", { className: "nre-experiment-evals", children: item.evalRows.map((row) => (_jsx(EvalAttempts, { row: row, attemptHref: attemptHref, locale: locale }, row.evalId))) })] })] }));
12
53
  }
13
- export function ExperimentList({ items, attemptHref, className, locale = DEFAULT_REPORT_LOCALE, }) {
14
- return (_jsxs("div", { className: cx("nre", "nre-experiment-list", className), children: [items.length === 0 && _jsx("p", { className: "nre-experiment-list-empty", children: localeText(locale, "attemptList.empty") }), _jsx("ul", { className: "nre-experiments", children: items.map((item) => (_jsx(ExperimentCard, { item: item, attemptHref: attemptHref, locale: locale }, item.experimentId))) })] }));
54
+ export function ExperimentList({ items, attemptHref, filter = false, className, locale = DEFAULT_REPORT_LOCALE, }) {
55
+ const labels = [
56
+ localeText(locale, "experimentList.experiment"),
57
+ localeText(locale, "table.model"),
58
+ localeText(locale, "table.agent"),
59
+ localeText(locale, "experimentList.avgDuration"),
60
+ localeText(locale, "experimentList.passRate"),
61
+ localeText(locale, "experimentList.tokens"),
62
+ localeText(locale, "experimentList.estimatedCost"),
63
+ localeText(locale, "experimentList.result"),
64
+ ];
65
+ const board = (_jsxs("div", { className: "nre-experiment-table", children: [_jsx("div", { className: "nre-experiment-head", children: labels.map((label, index) => (_jsx("button", { type: "button", "data-nre-experiment-sort": index, className: index === 4 ? "nre-sort-desc" : undefined, children: label }, label))) }), items.length === 0 && _jsx("p", { className: "nre-experiment-list-empty", children: localeText(locale, "attemptList.empty") }), items.map((item) => (_jsx(ExperimentRow, { item: item, attemptHref: attemptHref, locale: locale }, item.experimentId)))] }));
66
+ return (_jsxs("div", { className: cx("nre", "nre-experiment-list", filter && "nre-experiment-table-wrap", className), children: [filter && (_jsx("input", { className: "nre-filter", "data-nre-experiment-filter": "", type: "search", placeholder: localeText(locale, "experimentList.filterPlaceholder") })), board] }));
15
67
  }
@@ -40,10 +40,6 @@ export function MetricScatter({ data, pointHref, className, locale = DEFAULT_REP
40
40
  if (drawableRows.length === 0) {
41
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
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
43
  const axisLabel = (label, col) => `${label}${col.unit ? `(${col.unit})` : ""}`;
48
44
  const xScale = axisScale(drawableRows.map((r) => r.x.value), data.x.better, MARGIN.left, MARGIN.left + PLOT_W);
49
45
  // y 像素轴向下增长:better:"higher" 高值在上 → 映射到 [bottom, top];"lower" 由 axisScale 反向后同样落到上方
@@ -57,7 +53,7 @@ export function MetricScatter({ data, pointHref, className, locale = DEFAULT_REP
57
53
  label: pointLabel(r.key),
58
54
  xValue,
59
55
  yValue,
60
- // hover 内容:experiment(点键)+ 系列(series,CostPassRateComparison 传的是 agent 维度,
56
+ // hover 内容:experiment(点键)+ 系列(series,ExperimentComparison 传的是 agent 维度,
61
57
  // 有则加一行;无系列的散点没有这行)+ 两轴 display 与 samples/total(docs/feature/reports/library.md「MetricScatter」行为清单)
62
58
  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
59
  px: xScale.scale(xValue),
@@ -300,7 +300,6 @@ const failedAttempt = {
300
300
  durationMs: 32_000,
301
301
  costUSD: 0.12,
302
302
  locator: locator("@1a4a4a4"),
303
- capabilities: { eval: true, execution: true, timing: true, diff: false },
304
303
  };
305
304
  const erroredAttempt = {
306
305
  evalId: "geometry/angles",
@@ -312,7 +311,12 @@ const erroredAttempt = {
312
311
  assertions: [],
313
312
  durationMs: 4_500,
314
313
  locator: locator("@1c1c1c1"),
315
- capabilities: { eval: false, execution: true, timing: false, diff: false },
314
+ };
315
+ const failedRetryAttempt = {
316
+ ...failedAttempt,
317
+ attempt: 4,
318
+ durationMs: 35_000,
319
+ locator: locator("@1b5b5b5"),
316
320
  };
317
321
  export const attemptListItems = [failedAttempt, erroredAttempt];
318
322
  export const evalListItems = [
@@ -342,13 +346,14 @@ export const experimentListItems = [
342
346
  experimentId: "compare/bub",
343
347
  agent: "bub",
344
348
  model: "gpt-5.4",
349
+ flags: { memory: true },
345
350
  verdicts: { passed: 1, failed: 1, errored: 0, skipped: 0 },
346
351
  passRate: { value: 0.5, display: "50%", samples: 2, total: 2, refs: [] },
347
352
  cost: { value: 0.12, display: "$0.12", samples: 1, total: 2, refs: [failedAttempt.locator] },
348
353
  duration: { value: 32_000, display: "32.0s", samples: 2, total: 2, refs: [] },
349
354
  tokens: { value: null, display: "—", samples: 0, total: 2, refs: [] },
350
355
  evals: 2,
351
- attempts: 2,
356
+ attempts: 3,
352
357
  lastRunAt: "2026-07-01T10:00:00Z",
353
358
  evalRows: [
354
359
  {
@@ -357,7 +362,7 @@ export const experimentListItems = [
357
362
  reason: "roots-correct: expected x=2, got x=3",
358
363
  duration: { value: 32_000, display: "32.0s", samples: 1, total: 1, refs: [failedAttempt.locator] },
359
364
  cost: { value: 0.12, display: "$0.12", samples: 1, total: 1, refs: [failedAttempt.locator] },
360
- attempts: [failedAttempt],
365
+ attempts: [failedAttempt, failedRetryAttempt],
361
366
  },
362
367
  {
363
368
  evalId: "algebra/simple",
@@ -1,3 +1,3 @@
1
- export { MISSING_TEXT, capabilityBadge, formatDurationMs, formatPercent, formatUSD, verdictMark } from "../format.ts";
1
+ export { MISSING_TEXT, formatDurationMs, formatPercent, formatUSD, verdictMark } from "../format.ts";
2
2
  /** 拼 class 名:过滤空值,末尾接使用者透传的 className。 */
3
3
  export declare function cx(...parts: (string | undefined | false)[]): string;
@@ -1,6 +1,6 @@
1
1
  // web 面的小工具:展示格式化统一住在计算侧的 ../format.ts(两个渲染面同一份),
2
2
  // 这里 re-export 并补 class 名拼接。MetricCell 一律自带 display,组件不重算。
3
- export { MISSING_TEXT, capabilityBadge, formatDurationMs, formatPercent, formatUSD, verdictMark } from "../format.js";
3
+ export { MISSING_TEXT, formatDurationMs, formatPercent, formatUSD, verdictMark } from "../format.js";
4
4
  /** 拼 class 名:过滤空值,末尾接使用者透传的 className。 */
5
5
  export function cx(...parts) {
6
6
  return parts.filter(Boolean).join(" ");
@@ -1,6 +1,6 @@
1
1
  export { AttemptList, DeltaTable, EvalList, ExperimentList, GroupSummary, MetricBars, MetricLine, MetricMatrix, MetricScatter, MetricTable, RunOverview, Scoreboard, } from "../components.tsx";
2
2
  export type { AttemptListProps, DeltaTableProps, EvalListProps, ExperimentListProps, GroupSummaryProps, MetricLineProps, MetricMatrixProps, MetricScatterProps, MetricTableProps, RunOverviewProps, ScoreboardProps, } from "../components.tsx";
3
- export type { AttemptEvidenceCapabilities, AttemptListItem, AttemptLocator, DeltaData, EvalListItem, ExperimentListEvalRow, ExperimentListItem, GroupSummaryData, LineAxis, LineData, MatrixData, MetricCell, MetricColumn, OverviewData, ScatterData, ScoreboardData, SelectionWarning, TableData, TableRowMeta, } from "../types.ts";
3
+ export type { AttemptListItem, AttemptLocator, DeltaData, EvalListItem, ExperimentListEvalRow, ExperimentListItem, GroupSummaryData, LineAxis, LineData, MatrixData, MetricCell, MetricColumn, OverviewData, ScatterData, ScoreboardData, SelectionWarning, TableData, TableRowMeta, } from "../types.ts";
4
4
  export { DEFAULT_REPORT_LOCALE, resolveMetricLabel } from "../locale.ts";
5
5
  export type { LocalizedLabel, ReportLocale } from "../locale.ts";
6
6
  export { NRE_PALETTE, colorClassForKey, colorHexForKey, colorIndexForKey, seriesClassForKey } from "./colors.ts";