niceeval 0.5.4 → 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.
- package/INDEX.md +55 -0
- package/dist/agents/types.d.ts +317 -0
- package/dist/context/types.d.ts +335 -0
- package/dist/i18n/core.d.ts +11 -0
- package/dist/i18n/en.d.ts +181 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/zh-CN.d.ts +183 -0
- package/dist/o11y/execution-tree.d.ts +103 -0
- package/dist/o11y/otlp/parse.d.ts +3 -0
- package/dist/o11y/otlp/receiver.d.ts +21 -0
- package/dist/o11y/otlp/select.d.ts +22 -0
- package/dist/o11y/otlp/turn-otel.d.ts +43 -0
- package/dist/o11y/types.d.ts +190 -0
- package/dist/report/aggregate.d.ts +57 -0
- package/dist/report/aggregate.js +212 -0
- package/dist/report/built-ins/cost-pass-rate-comparison.d.ts +1 -0
- package/dist/report/built-ins/cost-pass-rate-comparison.js +17 -0
- package/dist/report/built-ins/index.d.ts +1 -0
- package/dist/report/built-ins/index.js +2 -0
- package/dist/report/components.d.ts +178 -0
- package/dist/report/components.js +132 -0
- package/dist/report/compute.d.ts +106 -0
- package/dist/report/compute.js +618 -0
- package/dist/report/flag.d.ts +6 -0
- package/dist/report/flag.js +8 -0
- package/dist/report/format.d.ts +32 -0
- package/dist/report/format.js +126 -0
- package/dist/report/index.d.ts +19 -0
- package/dist/report/index.js +30 -0
- package/dist/report/load.d.ts +12 -0
- package/dist/report/load.js +56 -0
- package/dist/report/locale.d.ts +83 -0
- package/dist/report/locale.js +160 -0
- package/dist/report/metrics.d.ts +21 -0
- package/dist/report/metrics.js +115 -0
- package/dist/report/primitives.d.ts +64 -0
- package/dist/report/primitives.js +113 -0
- package/dist/report/react/AttemptList.d.ts +23 -0
- package/dist/report/react/AttemptList.js +20 -0
- package/dist/report/react/DeltaTable.d.ts +8 -0
- package/dist/report/react/DeltaTable.js +24 -0
- package/dist/report/react/EvalList.d.ts +10 -0
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentList.d.ts +10 -0
- package/dist/report/react/ExperimentList.js +15 -0
- package/dist/report/react/GroupSummary.d.ts +8 -0
- package/dist/report/react/GroupSummary.js +8 -0
- package/dist/report/react/MetricBars.d.ts +10 -0
- package/dist/report/react/MetricBars.js +48 -0
- package/dist/report/react/MetricLine.d.ts +9 -0
- package/dist/report/react/MetricLine.js +68 -0
- package/dist/report/react/MetricMatrix.d.ts +10 -0
- package/dist/report/react/MetricMatrix.js +23 -0
- package/dist/report/react/MetricScatter.d.ts +9 -0
- package/dist/report/react/MetricScatter.js +100 -0
- package/dist/report/react/MetricTable.d.ts +11 -0
- package/dist/report/react/MetricTable.js +26 -0
- package/dist/report/react/RunOverview.d.ts +8 -0
- package/dist/report/react/RunOverview.js +12 -0
- package/dist/report/react/Scoreboard.d.ts +8 -0
- package/dist/report/react/Scoreboard.js +26 -0
- package/dist/report/react/cell.d.ts +9 -0
- package/dist/report/react/cell.js +9 -0
- package/dist/report/react/chart-math.d.ts +14 -0
- package/dist/report/react/chart-math.js +68 -0
- package/dist/report/react/colors.d.ts +14 -0
- package/dist/report/react/colors.js +45 -0
- package/dist/report/react/fixtures.d.ts +19 -0
- package/dist/report/react/fixtures.js +393 -0
- package/dist/report/react/format.d.ts +3 -0
- package/dist/report/react/format.js +7 -0
- package/dist/report/react/index.d.ts +6 -0
- package/dist/report/react/index.js +14 -0
- package/dist/report/report.d.ts +24 -0
- package/dist/report/report.js +49 -0
- package/dist/report/text/faces.d.ts +29 -0
- package/dist/report/text/faces.js +503 -0
- package/dist/report/text/layout.d.ts +75 -0
- package/dist/report/text/layout.js +173 -0
- package/dist/report/text/plot.d.ts +37 -0
- package/dist/report/text/plot.js +81 -0
- package/dist/report/text/table.d.ts +8 -0
- package/dist/report/text/table.js +86 -0
- package/dist/report/tree.d.ts +99 -0
- package/dist/report/tree.js +228 -0
- package/dist/report/types.d.ts +420 -0
- package/dist/report/types.js +4 -0
- package/dist/report/web.d.ts +15 -0
- package/dist/report/web.js +37 -0
- package/dist/results/annotated-source.d.ts +61 -0
- package/dist/results/attempt-evidence.d.ts +69 -0
- package/dist/results/attempt-source.d.ts +15 -0
- package/dist/results/locator.d.ts +75 -0
- package/dist/results/locator.js +0 -0
- package/dist/results/select.d.ts +45 -0
- package/dist/results/select.js +230 -0
- package/dist/results/source-hash.d.ts +9 -0
- package/dist/results/types.d.ts +204 -0
- package/dist/runner/eval-source.d.ts +22 -0
- package/dist/runner/fingerprint.d.ts +23 -0
- package/dist/runner/types.d.ts +332 -0
- package/dist/sandbox/docker-stream.d.ts +24 -0
- package/dist/sandbox/docker.d.ts +84 -0
- package/dist/sandbox/e2b.d.ts +31 -0
- package/dist/sandbox/errors.d.ts +4 -0
- package/dist/sandbox/local-files.d.ts +6 -0
- package/dist/sandbox/paths.d.ts +4 -0
- package/dist/sandbox/registry.d.ts +13 -0
- package/dist/sandbox/resolve.d.ts +40 -0
- package/dist/sandbox/retry.d.ts +10 -0
- package/dist/sandbox/shell.d.ts +11 -0
- package/dist/sandbox/source-files.d.ts +13 -0
- package/dist/sandbox/types.d.ts +203 -0
- package/dist/sandbox/vercel.d.ts +37 -0
- package/dist/scoring/types.d.ts +70 -0
- package/dist/shared/aggregate.d.ts +29 -0
- package/dist/shared/aggregate.js +55 -0
- package/dist/shared/types.d.ts +27 -0
- package/dist/shared/verdict.d.ts +27 -0
- package/dist/shared/verdict.js +34 -0
- package/dist/tty-line.d.ts +6 -0
- package/dist/types.d.ts +7 -0
- package/dist/util.d.ts +22 -0
- package/docs-site/zh/concepts/adapter.mdx +5 -5
- package/docs-site/zh/concepts/assert.mdx +8 -8
- package/docs-site/zh/concepts/evals.mdx +5 -5
- package/docs-site/zh/concepts/experiment.mdx +2 -2
- package/docs-site/zh/concepts/judge.mdx +9 -9
- package/docs-site/zh/concepts/overview.mdx +2 -2
- package/docs-site/zh/concepts/tier.mdx +6 -6
- package/docs-site/zh/example/claude-code-codex-plugin.mdx +9 -5
- package/docs-site/zh/example/claude-code-codex-skill.mdx +12 -7
- package/docs-site/zh/guides/agent-feedback-loop.mdx +235 -78
- package/docs-site/zh/guides/connect-otel.mdx +3 -3
- package/docs-site/zh/guides/connect-your-agent.mdx +2 -2
- package/docs-site/zh/guides/custom-reports.mdx +196 -36
- package/docs-site/zh/guides/experiments.mdx +7 -1
- package/docs-site/zh/guides/fixtures.mdx +1 -1
- package/docs-site/zh/guides/official-adapters.mdx +31 -9
- package/docs-site/zh/guides/publish-report.mdx +3 -3
- package/docs-site/zh/guides/report-components.mdx +194 -55
- package/docs-site/zh/guides/reporters.mdx +2 -2
- package/docs-site/zh/guides/results-data.mdx +35 -34
- package/docs-site/zh/guides/runner.mdx +2 -2
- package/docs-site/zh/guides/sandbox-agent.mdx +2 -2
- package/docs-site/zh/guides/{sandbox-backends.mdx → sandbox-providers.mdx} +11 -11
- package/docs-site/zh/guides/viewing-results.mdx +199 -79
- package/docs-site/zh/guides/write-experiment.mdx +9 -1
- package/docs-site/zh/guides/write-send.mdx +2 -2
- package/docs-site/zh/index.mdx +4 -3
- package/docs-site/zh/introduction.mdx +2 -1
- package/docs-site/zh/quickstart.mdx +5 -3
- package/docs-site/zh/reference/builtin-agents.mdx +38 -13
- package/docs-site/zh/reference/capabilities.mdx +1 -1
- package/docs-site/zh/reference/cli.mdx +36 -13
- package/docs-site/zh/reference/define-agent.mdx +12 -12
- package/docs-site/zh/reference/define-config.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +3 -3
- package/docs-site/zh/reference/events.mdx +7 -1
- package/docs-site/zh/reference/expect.mdx +3 -3
- package/package.json +10 -6
- package/src/agents/bub.ts +129 -42
- package/src/agents/claude-code.test.ts +152 -0
- package/src/agents/claude-code.ts +127 -13
- package/src/agents/codex.test.ts +192 -0
- package/src/agents/codex.ts +151 -12
- package/src/agents/index.ts +9 -3
- package/src/agents/manifest.ts +41 -0
- package/src/agents/plugin-config.test.ts +94 -0
- package/src/agents/shared.ts +33 -2
- package/src/agents/skills.test.ts +214 -0
- package/src/agents/skills.ts +304 -0
- package/src/agents/streaming.ts +5 -5
- package/src/agents/types.ts +54 -2
- package/src/cli.ts +74 -47
- package/src/context/context.test.ts +20 -2
- package/src/context/control-flow.ts +1 -1
- package/src/context/session.test.ts +92 -1
- package/src/context/session.ts +23 -1
- package/src/context/types.ts +4 -4
- package/src/define.test.ts +8 -8
- package/src/define.ts +11 -11
- package/src/expect/index.ts +5 -5
- package/src/i18n/en.ts +34 -15
- package/src/i18n/zh-CN.ts +37 -21
- package/src/index.ts +1 -1
- package/src/o11y/execution-tree.test.ts +452 -0
- package/src/o11y/execution-tree.ts +367 -0
- package/src/o11y/otlp/receiver.ts +1 -1
- package/src/o11y/otlp/select.ts +7 -3
- package/src/o11y/parsers/claude-code.test.ts +175 -0
- package/src/o11y/parsers/claude-code.ts +32 -0
- package/src/o11y/types.ts +6 -1
- package/src/report/aggregate.ts +26 -20
- package/src/report/built-in-user-parity.test.tsx +643 -0
- package/src/report/built-ins/cost-pass-rate-comparison.tsx +23 -0
- package/src/report/built-ins/index.ts +2 -0
- package/src/report/components.tsx +223 -50
- package/src/report/compute.ts +358 -53
- package/src/report/dual-render.test.tsx +1125 -0
- package/src/report/flag.ts +1 -1
- package/src/report/format.ts +50 -2
- package/src/report/index.ts +48 -14
- package/src/report/load.ts +1 -1
- package/src/report/locale.ts +206 -0
- package/src/report/metrics.ts +39 -15
- package/src/report/primitives.tsx +100 -2
- package/src/report/react/AttemptList.tsx +115 -0
- package/src/report/react/DeltaTable.tsx +9 -6
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentList.tsx +108 -0
- package/src/report/react/GroupSummary.tsx +66 -0
- package/src/report/react/MetricBars.tsx +24 -13
- package/src/report/react/MetricLine.tsx +21 -21
- package/src/report/react/MetricMatrix.tsx +8 -4
- package/src/report/react/MetricScatter.tsx +136 -96
- package/src/report/react/MetricTable.tsx +115 -15
- package/src/report/react/RunOverview.tsx +48 -26
- package/src/report/react/Scoreboard.tsx +15 -7
- package/src/report/react/cell.tsx +18 -9
- package/src/{view/app/lib/chart.ts → report/react/chart-math.ts} +2 -6
- package/src/report/react/colors.ts +24 -15
- package/src/report/react/enhance.js +129 -0
- package/src/report/react/fixtures.ts +163 -34
- package/src/report/react/format.ts +1 -1
- package/src/report/react/index.tsx +22 -6
- package/src/report/react/render.test.tsx +164 -31
- package/src/report/react/styles.css +405 -213
- package/src/report/report.test.ts +403 -94
- package/src/report/report.ts +29 -11
- package/src/report/text/faces.ts +303 -95
- package/src/report/text/layout.ts +76 -11
- package/src/report/text/table.ts +98 -0
- package/src/report/tree.ts +103 -20
- package/src/report/types.ts +196 -25
- package/src/report/web.ts +46 -19
- package/src/results/annotated-source.test.ts +146 -0
- package/src/results/annotated-source.ts +128 -0
- package/src/results/attempt-evidence.test.ts +158 -0
- package/src/results/attempt-evidence.ts +158 -0
- package/src/results/attempt-source.ts +48 -0
- package/src/results/copy.ts +154 -139
- package/src/results/format.ts +54 -47
- package/src/results/host-equivalence.test.ts +527 -0
- package/src/results/index.ts +56 -14
- package/src/results/locator.test.ts +198 -0
- package/src/results/locator.ts +0 -0
- package/src/results/open.ts +392 -175
- package/src/results/results.test.ts +943 -494
- package/src/results/select.ts +129 -23
- package/src/results/skipped-notice.ts +0 -0
- package/src/results/source-hash.ts +28 -0
- package/src/results/types.ts +98 -62
- package/src/results/writer.ts +304 -176
- package/src/runner/attempt.test.ts +170 -0
- package/src/runner/attempt.ts +48 -13
- package/src/runner/discover.test.ts +65 -0
- package/src/runner/discover.ts +6 -2
- package/src/runner/eval-source.test.ts +121 -0
- package/src/runner/eval-source.ts +45 -0
- package/src/runner/fingerprint.ts +54 -1
- package/src/runner/remote-sandbox.ts +1 -1
- package/src/runner/report.test.ts +6 -8
- package/src/runner/report.ts +5 -4
- package/src/runner/reporters/artifacts.ts +27 -41
- package/src/runner/reporters/braintrust.test.ts +3 -3
- package/src/runner/reporters/braintrust.ts +1 -1
- package/src/runner/reporters/console.ts +6 -6
- package/src/runner/reporters/json.ts +4 -4
- package/src/runner/reporters/live.test.ts +56 -0
- package/src/runner/reporters/live.ts +36 -10
- package/src/runner/reporters/quiet.test.ts +66 -0
- package/src/runner/reporters/quiet.ts +49 -0
- package/src/runner/reporters/shared.ts +5 -5
- package/src/runner/reporters/table.ts +17 -17
- package/src/runner/run.ts +61 -102
- package/src/runner/types.ts +43 -32
- package/src/sandbox/checkpoint.ts +1 -1
- package/src/sandbox/docker.ts +15 -2
- package/src/sandbox/e2b.ts +11 -5
- package/src/sandbox/errors.ts +13 -0
- package/src/sandbox/index.ts +3 -3
- package/src/sandbox/local-files.ts +1 -1
- package/src/sandbox/registry.ts +6 -5
- package/src/sandbox/resolve.ts +38 -23
- package/src/sandbox/retry.test.ts +68 -0
- package/src/sandbox/retry.ts +44 -0
- package/src/sandbox/types.ts +24 -24
- package/src/sandbox/vercel.ts +17 -3
- package/src/scoring/collector.ts +3 -3
- package/src/scoring/judge.ts +2 -2
- package/src/scoring/scoped.ts +13 -1
- package/src/scoring/types.ts +7 -7
- package/src/scoring/verdict.ts +4 -4
- package/src/shared/aggregate.ts +23 -3
- package/src/shared/types.ts +2 -2
- package/src/shared/{outcome.ts → verdict.ts} +15 -15
- package/src/show/compose.ts +12 -132
- package/src/show/index.ts +125 -94
- package/src/show/render.ts +660 -160
- package/src/show/show.test.ts +472 -253
- package/src/source-loc.ts +1 -1
- package/src/tty-line.ts +32 -0
- package/src/types.ts +2 -2
- package/src/util.test.ts +21 -1
- package/src/util.ts +5 -1
- package/src/view/app/App.tsx +33 -198
- package/src/view/app/components/AttemptModal.tsx +6 -6
- package/src/view/app/components/CodeView.tsx +2 -2
- package/src/view/app/components/CopyControls.tsx +32 -36
- package/src/view/app/components/SkippedRunsBanner.tsx +140 -0
- package/src/view/app/components/ui/badge.tsx +1 -1
- package/src/view/app/components/ui/dialog.tsx +2 -2
- package/src/view/app/i18n.ts +40 -31
- package/src/view/app/lib/artifact-url.ts +1 -1
- package/src/view/app/lib/attempt-route.test.ts +37 -47
- package/src/view/app/lib/attempt-route.ts +26 -26
- package/src/view/app/lib/rows.ts +9 -112
- package/src/view/app/lib/verdict.ts +25 -0
- package/src/view/app/main.tsx +9 -12
- package/src/view/app/pages/RunsPage.tsx +4 -4
- package/src/view/app/pages/TracesPage.tsx +3 -3
- package/src/view/app/shared.ts +0 -1
- package/src/view/app/types.ts +6 -51
- package/src/view/artifact-serving.test.ts +136 -0
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +21 -21
- package/src/view/data.test.ts +214 -143
- package/src/view/data.ts +166 -120
- package/src/view/index.ts +23 -11
- package/src/view/server.ts +52 -17
- package/src/view/shared/types.ts +22 -31
- package/src/view/styles.css +117 -327
- package/src/view/view-report.test.ts +146 -98
- package/src/report/default-report.tsx +0 -222
- package/src/report/dual-face.test.tsx +0 -527
- package/src/report/react/CaseList.tsx +0 -70
- package/src/view/app/components/CostScoreChart.tsx +0 -147
- package/src/view/app/components/ExperimentTable.tsx +0 -284
- package/src/view/app/components/GroupSelector.tsx +0 -61
- package/src/view/app/components/primitives.tsx +0 -43
- package/src/view/app/lib/outcome.ts +0 -82
|
@@ -1,54 +1,68 @@
|
|
|
1
|
-
// MetricScatter:质量 × 成本 frontier 的积木,内联 SVG
|
|
1
|
+
// MetricScatter:质量 × 成本 frontier 的积木,内联 SVG、零图表库、零 hooks。
|
|
2
2
|
// 轴向随 better:"lower" 的轴反向画,「好」的角落恒在右上(成本轴 $20 → $0 就是这么来的);
|
|
3
|
-
//
|
|
4
|
-
//
|
|
3
|
+
// niceTicks 刻度 + 网格线;每个点直接标注(防撞布局,被挤开时补 leader line);
|
|
4
|
+
// 同系列的点按 x 值排序连线,系列图例列在图下。x 或 y 为 null 的点不画,
|
|
5
|
+
// 底部注脚如实报「n 个点缺数据」;hover 信息退化为 SVG <title>,不 hydrate 也在
|
|
6
|
+
// (enhance.js 在场时升级为样式化 tooltip)。配色走类名(nre-series-cN)由 CSS 上色,
|
|
7
|
+
// 深色主题下图表随令牌切换,不留内联 hex。
|
|
5
8
|
|
|
6
9
|
import type { ReactElement } from "react";
|
|
7
10
|
import type { MetricColumn, ScatterData } from "../types.ts";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
11
|
+
import { formatMetricValue } from "../format.ts";
|
|
12
|
+
import { DEFAULT_REPORT_LOCALE, countText, localeText, resolveMetricLabel, type ReportLocale } from "../locale.ts";
|
|
13
|
+
import { layoutLabelOffsets, niceTicks } from "./chart-math.ts";
|
|
14
|
+
import { colorClassForKey, seriesClassForKey } from "./colors.ts";
|
|
15
|
+
import { cx } from "./format.ts";
|
|
10
16
|
|
|
11
|
-
// 画布与边距:右侧留白给系列名,底部给 x 轴标签
|
|
12
17
|
const WIDTH = 640;
|
|
13
|
-
const HEIGHT =
|
|
14
|
-
const
|
|
18
|
+
const HEIGHT = 360;
|
|
19
|
+
const MARGIN = { top: 26, right: 24, bottom: 46, left: 62 };
|
|
20
|
+
const PLOT_W = WIDTH - MARGIN.left - MARGIN.right;
|
|
21
|
+
const PLOT_H = HEIGHT - MARGIN.top - MARGIN.bottom;
|
|
15
22
|
|
|
16
23
|
/** 可画的点:x/y 都有值。组件内部的整理结果,不改数据。 */
|
|
17
24
|
interface DrawablePoint {
|
|
18
25
|
key: string;
|
|
19
26
|
series?: string;
|
|
27
|
+
label: string;
|
|
20
28
|
xValue: number;
|
|
21
29
|
yValue: number;
|
|
22
|
-
xDisplay: string;
|
|
23
|
-
yDisplay: string;
|
|
24
30
|
title: string;
|
|
25
31
|
px: number;
|
|
26
32
|
py: number;
|
|
27
33
|
}
|
|
28
34
|
|
|
29
|
-
/**
|
|
35
|
+
/** 点的直接标签:experiment id 的末段(完整 id 在 <title> 里)。 */
|
|
36
|
+
function pointLabel(key: string): string {
|
|
37
|
+
return key.split("/").filter(Boolean).at(-1) ?? key;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 一根轴:niceTicks 撑出整齐的值域,值 → 像素做线性映射;
|
|
42
|
+
* better: "lower" 时反向,好的一端固定在右 / 上。
|
|
43
|
+
*/
|
|
30
44
|
function axisScale(values: number[], better: MetricColumn["better"], pixelLo: number, pixelHi: number) {
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
const span = hi - lo || 2;
|
|
35
|
-
const padded = { lo: lo - (hi - lo ? span * 0.08 : 1), hi: hi + (hi - lo ? span * 0.08 : 1) };
|
|
45
|
+
const ticks = niceTicks(Math.min(...values), Math.max(...values), 5);
|
|
46
|
+
const lo = ticks[0];
|
|
47
|
+
const hi = ticks[ticks.length - 1];
|
|
36
48
|
const scale = (v: number) => {
|
|
37
|
-
let t = (v -
|
|
49
|
+
let t = (v - lo) / (hi - lo || 1);
|
|
38
50
|
if (better === "lower") t = 1 - t; // 反向:值越低越靠「好」的一端
|
|
39
51
|
return pixelLo + t * (pixelHi - pixelLo);
|
|
40
52
|
};
|
|
41
|
-
return {
|
|
53
|
+
return { ticks, scale };
|
|
42
54
|
}
|
|
43
55
|
|
|
44
56
|
export function MetricScatter({
|
|
45
57
|
data,
|
|
46
58
|
pointHref,
|
|
47
59
|
className,
|
|
60
|
+
locale = DEFAULT_REPORT_LOCALE,
|
|
48
61
|
}: {
|
|
49
62
|
data: ScatterData;
|
|
50
63
|
pointHref?: (row: ScatterData["rows"][number]) => string;
|
|
51
64
|
className?: string;
|
|
65
|
+
locale?: ReportLocale;
|
|
52
66
|
}): ReactElement {
|
|
53
67
|
const missing = data.rows.filter((r) => r.x.value === null || r.y.value === null);
|
|
54
68
|
const drawableRows = data.rows.filter((r) => r.x.value !== null && r.y.value !== null);
|
|
@@ -56,23 +70,39 @@ export function MetricScatter({
|
|
|
56
70
|
const missingNote =
|
|
57
71
|
missing.length > 0 ? (
|
|
58
72
|
<p className="nre-scatter-missing" title={missing.map((r) => r.key).join(", ")}>
|
|
59
|
-
{
|
|
73
|
+
{countText(locale, "pointsMissing", missing.length)}
|
|
60
74
|
</p>
|
|
61
75
|
) : null;
|
|
62
76
|
|
|
63
|
-
|
|
77
|
+
const xLabel = resolveMetricLabel(data.x.label, locale, data.x.key);
|
|
78
|
+
const yLabel = resolveMetricLabel(data.y.label, locale, data.y.key);
|
|
79
|
+
|
|
80
|
+
// 0 个可画点:x/y 指标没有可用数据 —— 明确说缺哪两个指标,不画一张空坐标系
|
|
81
|
+
// (与表格的「绝不画 0」同态度)。
|
|
64
82
|
if (drawableRows.length === 0) {
|
|
65
83
|
return (
|
|
66
84
|
<figure className={cx("nre", "nre-metric-scatter", className)}>
|
|
67
|
-
<p className="nre-missing">{
|
|
85
|
+
<p className="nre-scatter-empty nre-missing">{localeText(locale, "scatter.noData", { x: xLabel, y: yLabel })}</p>
|
|
86
|
+
{missingNote}
|
|
87
|
+
</figure>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 恰好 1 个可画点:成本 × 通过率的对比至少要两个实验,单点不成图 —— 显式说清而不是画个孤点。
|
|
92
|
+
if (drawableRows.length === 1) {
|
|
93
|
+
return (
|
|
94
|
+
<figure className={cx("nre", "nre-metric-scatter", className)}>
|
|
95
|
+
<p className="nre-scatter-empty">{localeText(locale, "scatter.needTwo", { x: xLabel, y: yLabel })}</p>
|
|
68
96
|
{missingNote}
|
|
69
97
|
</figure>
|
|
70
98
|
);
|
|
71
99
|
}
|
|
72
100
|
|
|
73
|
-
const
|
|
101
|
+
const axisLabel = (label: string, col: MetricColumn) => `${label}${col.unit ? `(${col.unit})` : ""}`;
|
|
102
|
+
|
|
103
|
+
const xScale = axisScale(drawableRows.map((r) => r.x.value as number), data.x.better, MARGIN.left, MARGIN.left + PLOT_W);
|
|
74
104
|
// y 像素轴向下增长:better:"higher" 高值在上 → 映射到 [bottom, top];"lower" 由 axisScale 反向后同样落到上方
|
|
75
|
-
const yScale = axisScale(drawableRows.map((r) => r.y.value as number), data.y.better,
|
|
105
|
+
const yScale = axisScale(drawableRows.map((r) => r.y.value as number), data.y.better, MARGIN.top + PLOT_H, MARGIN.top);
|
|
76
106
|
|
|
77
107
|
const points: DrawablePoint[] = drawableRows.map((r) => {
|
|
78
108
|
const xValue = r.x.value as number;
|
|
@@ -80,12 +110,12 @@ export function MetricScatter({
|
|
|
80
110
|
return {
|
|
81
111
|
key: r.key,
|
|
82
112
|
series: r.series,
|
|
113
|
+
label: pointLabel(r.key),
|
|
83
114
|
xValue,
|
|
84
115
|
yValue,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
title: `${r.key}\n${data.x.label}: ${r.x.display}(${r.x.samples}/${r.x.total})\n${data.y.label}: ${r.y.display}(${r.y.samples}/${r.y.total})`,
|
|
116
|
+
// hover 内容:experiment(点键)+ 系列(series,CostPassRateComparison 传的是 agent 维度,
|
|
117
|
+
// 有则加一行;无系列的散点没有这行)+ 两轴 display 与 samples/total(docs/feature/reports/library.md「MetricScatter」行为清单)
|
|
118
|
+
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})`,
|
|
89
119
|
px: xScale.scale(xValue),
|
|
90
120
|
py: yScale.scale(yValue),
|
|
91
121
|
};
|
|
@@ -104,14 +134,12 @@ export function MetricScatter({
|
|
|
104
134
|
}
|
|
105
135
|
for (const list of bySeries.values()) list.sort((a, b) => a.xValue - b.xValue);
|
|
106
136
|
|
|
107
|
-
//
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const axisLabel = (col: MetricColumn) => `${col.label}${col.unit ? `(${col.unit})` : ""}`;
|
|
137
|
+
// 直接标签的防撞布局:靠右的点把标签锚到左侧,重叠的往下推、补 leader line
|
|
138
|
+
const positions = points.map((p) => {
|
|
139
|
+
const anchorLeft = p.px >= MARGIN.left + PLOT_W * 0.72;
|
|
140
|
+
return { cx: p.px, cy: p.py, anchorLeft, width: p.label.length * 6.4 + 10 };
|
|
141
|
+
});
|
|
142
|
+
const labelOffsets = layoutLabelOffsets(positions);
|
|
115
143
|
|
|
116
144
|
return (
|
|
117
145
|
<figure className={cx("nre", "nre-metric-scatter", className)}>
|
|
@@ -119,98 +147,110 @@ export function MetricScatter({
|
|
|
119
147
|
className="nre-scatter-svg"
|
|
120
148
|
viewBox={`0 0 ${WIDTH} ${HEIGHT}`}
|
|
121
149
|
role="img"
|
|
122
|
-
aria-label={`${axisLabel(data.x)} × ${axisLabel(data.y)}`}
|
|
150
|
+
aria-label={`${axisLabel(xLabel, data.x)} × ${axisLabel(yLabel, data.y)}`}
|
|
123
151
|
>
|
|
124
|
-
{/*
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
152
|
+
{/* 网格:niceTicks 的整齐刻度线,颜色走 CSS(var(--line)) */}
|
|
153
|
+
<g className="nre-scatter-grid">
|
|
154
|
+
{yScale.ticks.map((tick) => (
|
|
155
|
+
<line key={`gy${tick}`} x1={MARGIN.left} x2={MARGIN.left + PLOT_W} y1={yScale.scale(tick)} y2={yScale.scale(tick)} />
|
|
156
|
+
))}
|
|
157
|
+
{xScale.ticks.map((tick) => (
|
|
158
|
+
<line key={`gx${tick}`} y1={MARGIN.top} y2={MARGIN.top + PLOT_H} x1={xScale.scale(tick)} x2={xScale.scale(tick)} />
|
|
159
|
+
))}
|
|
160
|
+
</g>
|
|
161
|
+
|
|
134
162
|
{/* 「好」的角落恒在右上:轴向已按 better 反转,这里只是把这句契约写在图上 */}
|
|
135
|
-
<text className="nre-scatter-better-hint" x={
|
|
136
|
-
|
|
163
|
+
<text className="nre-scatter-better-hint" x={MARGIN.left + PLOT_W - 6} y={MARGIN.top + 14} textAnchor="end">
|
|
164
|
+
{localeText(locale, "scatter.betterHint")}
|
|
137
165
|
</text>
|
|
138
166
|
|
|
167
|
+
{/* 刻度:已格式化的整齐值(formatMetricValue 与计算侧同一套) */}
|
|
168
|
+
<g className="nre-scatter-axis nre-scatter-axis-y">
|
|
169
|
+
{yScale.ticks.map((tick) => (
|
|
170
|
+
<text key={`ay${tick}`} className="nre-scatter-tick" x={MARGIN.left - 8} y={yScale.scale(tick) + 3} textAnchor="end">
|
|
171
|
+
{formatMetricValue(tick, data.y.unit)}
|
|
172
|
+
</text>
|
|
173
|
+
))}
|
|
174
|
+
</g>
|
|
175
|
+
<g className="nre-scatter-axis nre-scatter-axis-x">
|
|
176
|
+
{xScale.ticks.map((tick) => (
|
|
177
|
+
<text key={`ax${tick}`} className="nre-scatter-tick" x={xScale.scale(tick)} y={MARGIN.top + PLOT_H + 16} textAnchor="middle">
|
|
178
|
+
{formatMetricValue(tick, data.x.unit)}
|
|
179
|
+
</text>
|
|
180
|
+
))}
|
|
181
|
+
</g>
|
|
182
|
+
|
|
139
183
|
{/* 轴标签 */}
|
|
140
|
-
<text className="nre-scatter-xlabel" x={
|
|
141
|
-
{axisLabel(data.x)}
|
|
184
|
+
<text className="nre-scatter-xlabel" x={MARGIN.left + PLOT_W / 2} y={HEIGHT - 8} textAnchor="middle">
|
|
185
|
+
{axisLabel(xLabel, data.x)}
|
|
142
186
|
</text>
|
|
143
187
|
<text
|
|
144
188
|
className="nre-scatter-ylabel"
|
|
145
|
-
x={
|
|
146
|
-
y={
|
|
189
|
+
x={14}
|
|
190
|
+
y={MARGIN.top + PLOT_H / 2}
|
|
147
191
|
textAnchor="middle"
|
|
148
|
-
|
|
149
|
-
fill="#525252"
|
|
150
|
-
transform={`rotate(-90 16 ${(PLOT.top + PLOT.bottom) / 2})`}
|
|
192
|
+
transform={`rotate(-90 14 ${MARGIN.top + PLOT_H / 2})`}
|
|
151
193
|
>
|
|
152
|
-
{axisLabel(data.y)}
|
|
194
|
+
{axisLabel(yLabel, data.y)}
|
|
153
195
|
</text>
|
|
154
196
|
|
|
155
|
-
{/*
|
|
156
|
-
{xTicks.map((v) => (
|
|
157
|
-
<text key={`x${v}`} className="nre-scatter-tick" x={xScale.scale(v)} y={PLOT.bottom + 16} textAnchor="middle" fontSize={11} fill="#737373">
|
|
158
|
-
{displayFor("x", v)}
|
|
159
|
-
</text>
|
|
160
|
-
))}
|
|
161
|
-
{yTicks.map((v) => (
|
|
162
|
-
<text key={`y${v}`} className="nre-scatter-tick" x={PLOT.left - 6} y={yScale.scale(v) + 4} textAnchor="end" fontSize={11} fill="#737373">
|
|
163
|
-
{displayFor("y", v)}
|
|
164
|
-
</text>
|
|
165
|
-
))}
|
|
166
|
-
|
|
167
|
-
{/* 系列连线 + 线旁的系列名(标在视觉上最靠右的点旁) */}
|
|
197
|
+
{/* 系列连线:类名上色(nre-series-cN),深色主题跟随 */}
|
|
168
198
|
{seriesOrder.map((series) => {
|
|
169
199
|
const list = bySeries.get(series)!;
|
|
170
|
-
|
|
171
|
-
const labelAt = list.reduce((a, b) => (b.px > a.px ? b : a));
|
|
200
|
+
if (list.length < 2) return null;
|
|
172
201
|
return (
|
|
173
|
-
<
|
|
174
|
-
{
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
strokeWidth={1.5}
|
|
181
|
-
/>
|
|
182
|
-
)}
|
|
183
|
-
<text className="nre-scatter-series-label" x={labelAt.px + 8} y={labelAt.py + 4} fontSize={12} fill={color}>
|
|
184
|
-
{series}
|
|
185
|
-
</text>
|
|
186
|
-
</g>
|
|
202
|
+
<polyline
|
|
203
|
+
key={series}
|
|
204
|
+
className={cx("nre-scatter-line", seriesClassForKey(series))}
|
|
205
|
+
data-series={series}
|
|
206
|
+
points={list.map((p) => `${p.px},${p.py}`).join(" ")}
|
|
207
|
+
fill="none"
|
|
208
|
+
/>
|
|
187
209
|
);
|
|
188
210
|
})}
|
|
189
211
|
|
|
190
|
-
{/*
|
|
212
|
+
{/* 点:g 内带 <title>(无 JS 的原生 hover)、直接标签与 leader line;
|
|
213
|
+
pointHref 时包普通 <a>,静态导出也能下钻 */}
|
|
191
214
|
{points.map((p, i) => {
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
215
|
+
const { anchorLeft } = positions[i];
|
|
216
|
+
const labelX = p.px + (anchorLeft ? -10 : 10);
|
|
217
|
+
const labelY = p.py + 4 + labelOffsets[i];
|
|
218
|
+
const group = (
|
|
219
|
+
<g
|
|
220
|
+
className={cx("nre-scatter-point", p.series !== undefined ? seriesClassForKey(p.series) : "nre-series-none")}
|
|
195
221
|
data-key={p.key}
|
|
196
|
-
cx={p.px}
|
|
197
|
-
cy={p.py}
|
|
198
|
-
r={4.5}
|
|
199
|
-
fill={p.series !== undefined ? colorHexForKey(p.series) : "#525252"}
|
|
200
222
|
>
|
|
201
223
|
<title>{p.title}</title>
|
|
202
|
-
|
|
224
|
+
{/* 标签被挤开时补一条 leader line,避免脱离原点看不出对应关系 */}
|
|
225
|
+
{labelOffsets[i] !== 0 && <line className="nre-leader" x1={p.px} y1={p.py} x2={labelX} y2={labelY - 4} />}
|
|
226
|
+
<circle className="nre-scatter-hit" cx={p.px} cy={p.py} r={12} />
|
|
227
|
+
<circle className="nre-scatter-dot" cx={p.px} cy={p.py} r={4.5} />
|
|
228
|
+
<text className="nre-scatter-point-label" x={labelX} y={labelY} textAnchor={anchorLeft ? "end" : "start"}>
|
|
229
|
+
{p.label}
|
|
230
|
+
</text>
|
|
231
|
+
</g>
|
|
203
232
|
);
|
|
204
233
|
const row = drawableRows[i];
|
|
205
234
|
return pointHref ? (
|
|
206
235
|
<a key={p.key} className="nre-scatter-point-link" href={pointHref(row)}>
|
|
207
|
-
{
|
|
236
|
+
{group}
|
|
208
237
|
</a>
|
|
209
238
|
) : (
|
|
210
|
-
<g key={p.key}>{
|
|
239
|
+
<g key={p.key}>{group}</g>
|
|
211
240
|
);
|
|
212
241
|
})}
|
|
213
242
|
</svg>
|
|
243
|
+
|
|
244
|
+
{/* 系列图例:同键同色(与其它块的稳定散列一致) */}
|
|
245
|
+
{seriesOrder.length > 0 && (
|
|
246
|
+
<figcaption className="nre-scatter-legend">
|
|
247
|
+
{seriesOrder.map((series) => (
|
|
248
|
+
<span key={series} className={cx("nre-legend-key", "nre-key", colorClassForKey(series))}>
|
|
249
|
+
{series}
|
|
250
|
+
</span>
|
|
251
|
+
))}
|
|
252
|
+
</figcaption>
|
|
253
|
+
)}
|
|
214
254
|
{missingNote}
|
|
215
255
|
</figure>
|
|
216
256
|
);
|
|
@@ -1,71 +1,171 @@
|
|
|
1
|
-
// MetricTable:行维度 ×
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// MetricTable:行维度 × 指标列。没有实体下钻——要展开到 experiment 的 Eval 或 Eval 的
|
|
2
|
+
// Attempt,用 ExperimentList / EvalList,这个组件只表达任意维度 × 任意指标。
|
|
3
|
+
// 行按传入顺序渲染——排序发生在计算侧(tableData 的 sort 参数),组件不重排;
|
|
4
|
+
// 静态 HTML 以数据侧预排的顺序呈现即完整。web 面额外输出渐进增强的 data 属性:
|
|
5
|
+
// 所有表头带 data-nre-sort、格子带 data-sort-value,enhance.js 在场时点表头
|
|
6
|
+
// 可就地重排(纯展示态交互,不改口径);filter 开时在表格前渲染过滤输入框。
|
|
7
|
+
// meta 在场(rows: "experiment")时补 Model / Agent / Verdicts 列,列序对齐 view
|
|
8
|
+
// 原生榜单:experiment、model、agent、指标列…、verdicts。
|
|
4
9
|
// 列头以箭头标注 better 方向;samples < total 的格子带覆盖率角标;
|
|
5
10
|
// 一组全 null 渲染成「缺数据」,绝不画 0(逻辑在 MetricCellView)。
|
|
6
11
|
|
|
7
12
|
import type { ReactElement } from "react";
|
|
8
|
-
import type {
|
|
13
|
+
import type { TableData, TableRowMeta } from "../types.ts";
|
|
14
|
+
import type { AttemptLocator } from "../../results/locator.ts";
|
|
15
|
+
import { DEFAULT_REPORT_LOCALE, localeText, resolveMetricLabel, type ReportLocale } from "../locale.ts";
|
|
9
16
|
import { MetricCellView } from "./cell.tsx";
|
|
10
17
|
import { colorClassForKey } from "./colors.ts";
|
|
11
18
|
import { cx } from "./format.ts";
|
|
12
19
|
|
|
20
|
+
/** verdict 计票 pill(「3 passed / 1 failed」):非零判定各一枚,全零如实空。 */
|
|
21
|
+
function VerdictTally({
|
|
22
|
+
verdicts,
|
|
23
|
+
locale,
|
|
24
|
+
}: {
|
|
25
|
+
verdicts: NonNullable<TableRowMeta["verdicts"]>;
|
|
26
|
+
locale: ReportLocale;
|
|
27
|
+
}): ReactElement {
|
|
28
|
+
const kinds = (["passed", "failed", "errored", "skipped"] as const).filter((k) => verdicts[k] > 0);
|
|
29
|
+
return (
|
|
30
|
+
<span className="nre-verdict-tally">
|
|
31
|
+
{kinds.map((kind) => (
|
|
32
|
+
<span key={kind} className={cx("nre-verdict-pill", `nre-verdict-${kind}`)}>
|
|
33
|
+
{verdicts[kind]} {localeText(locale, `verdict.${kind}`)}
|
|
34
|
+
</span>
|
|
35
|
+
))}
|
|
36
|
+
{kinds.length === 0 && <span className="nre-missing">—</span>}
|
|
37
|
+
</span>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
13
41
|
export function MetricTable({
|
|
14
42
|
data,
|
|
15
43
|
attemptHref,
|
|
44
|
+
filter,
|
|
16
45
|
className,
|
|
46
|
+
locale = DEFAULT_REPORT_LOCALE,
|
|
17
47
|
}: {
|
|
18
48
|
data: TableData;
|
|
19
|
-
attemptHref?: (
|
|
49
|
+
attemptHref?: (locator: AttemptLocator) => string;
|
|
50
|
+
filter?: boolean;
|
|
20
51
|
className?: string;
|
|
52
|
+
locale?: ReportLocale;
|
|
21
53
|
}): ReactElement {
|
|
22
|
-
|
|
23
|
-
|
|
54
|
+
const hasMeta = data.rows.some((row) => row.meta !== undefined);
|
|
55
|
+
const hasModel = data.rows.some((row) => row.meta?.model !== undefined);
|
|
56
|
+
const hasVerdicts = data.rows.some((row) => row.meta?.verdicts !== undefined);
|
|
57
|
+
|
|
58
|
+
const table = (
|
|
59
|
+
<table className={cx("nre", "nre-metric-table", !filter && className)}>
|
|
24
60
|
<thead>
|
|
25
61
|
<tr>
|
|
26
|
-
<th scope="col" className="nre-dimension">
|
|
62
|
+
<th scope="col" className="nre-dimension" data-nre-sort="">
|
|
27
63
|
{data.dimension}
|
|
28
64
|
</th>
|
|
65
|
+
{hasMeta && hasModel && (
|
|
66
|
+
<th scope="col" className="nre-meta-col" data-nre-sort="">
|
|
67
|
+
{localeText(locale, "table.model")}
|
|
68
|
+
</th>
|
|
69
|
+
)}
|
|
70
|
+
{hasMeta && (
|
|
71
|
+
<th scope="col" className="nre-meta-col" data-nre-sort="">
|
|
72
|
+
{localeText(locale, "table.agent")}
|
|
73
|
+
</th>
|
|
74
|
+
)}
|
|
29
75
|
{data.columns.map((col) => (
|
|
30
|
-
<th scope="col" key={col.key} className="nre-metric-col">
|
|
31
|
-
{col.label}
|
|
76
|
+
<th scope="col" key={col.key} className="nre-metric-col" data-nre-sort="">
|
|
77
|
+
{resolveMetricLabel(col.label, locale, col.key)}
|
|
32
78
|
{col.unit && <span className="nre-unit">({col.unit})</span>}
|
|
33
79
|
{/* better 方向提示:↑ 越高越好 / ↓ 越低越好 */}
|
|
34
80
|
{col.better && (
|
|
35
81
|
<span
|
|
36
82
|
className="nre-better"
|
|
37
|
-
title={col.better === "higher" ? "
|
|
83
|
+
title={localeText(locale, col.better === "higher" ? "table.higherBetter" : "table.lowerBetter")}
|
|
38
84
|
>
|
|
39
85
|
{col.better === "higher" ? "↑" : "↓"}
|
|
40
86
|
</span>
|
|
41
87
|
)}
|
|
42
88
|
</th>
|
|
43
89
|
))}
|
|
90
|
+
{hasVerdicts && (
|
|
91
|
+
<th scope="col" className="nre-verdicts-col" data-nre-sort="">
|
|
92
|
+
{localeText(locale, "table.verdicts")}
|
|
93
|
+
</th>
|
|
94
|
+
)}
|
|
44
95
|
</tr>
|
|
45
96
|
</thead>
|
|
46
97
|
<tbody>
|
|
47
98
|
{data.rows.map((row) => (
|
|
48
99
|
<tr key={row.key}>
|
|
49
100
|
{/* 行键 = 维度键(如 agent):稳定散列上色,跨块同键同色 */}
|
|
50
|
-
<th
|
|
101
|
+
<th
|
|
102
|
+
scope="row"
|
|
103
|
+
className={cx("nre-row-key", "nre-key", colorClassForKey(row.key))}
|
|
104
|
+
data-sort-value={row.key}
|
|
105
|
+
>
|
|
51
106
|
{row.key}
|
|
107
|
+
{/* rows: "experiment" 专属:eval/attempt 数 + 最后运行时间,行键下的一行紧凑摘要
|
|
108
|
+
(旧 ExperimentRow 的 "N eval results · N runs · 最后运行时间") */}
|
|
109
|
+
{row.meta?.evals !== undefined && (
|
|
110
|
+
<div className="nre-row-meta-sub">
|
|
111
|
+
{localeText(locale, "overview.evalsCount", { n: row.meta.evals })}
|
|
112
|
+
{row.meta.attempts !== undefined && row.meta.attempts > row.meta.evals
|
|
113
|
+
? ` · ${localeText(locale, "overview.attemptsCount", { n: row.meta.attempts })}`
|
|
114
|
+
: ""}
|
|
115
|
+
{row.meta.lastRunAt ? ` · ${localeText(locale, "latestRun", { run: row.meta.lastRunAt })}` : ""}
|
|
116
|
+
</div>
|
|
117
|
+
)}
|
|
52
118
|
</th>
|
|
119
|
+
{hasMeta && hasModel && (
|
|
120
|
+
<td className="nre-td nre-meta-cell" data-sort-value={row.meta?.model ?? ""}>
|
|
121
|
+
{row.meta?.model ?? <span className="nre-missing">—</span>}
|
|
122
|
+
</td>
|
|
123
|
+
)}
|
|
124
|
+
{hasMeta && (
|
|
125
|
+
<td className="nre-td nre-meta-cell" data-sort-value={row.meta?.agent ?? ""}>
|
|
126
|
+
{row.meta?.agent ?? <span className="nre-missing">—</span>}
|
|
127
|
+
</td>
|
|
128
|
+
)}
|
|
53
129
|
{data.columns.map((col) => {
|
|
54
130
|
const cell = row.cells[col.key];
|
|
55
131
|
return (
|
|
56
|
-
<td key={col.key} className="nre-td">
|
|
132
|
+
<td key={col.key} className="nre-td" data-sort-value={cell?.value ?? ""}>
|
|
57
133
|
{cell ? (
|
|
58
|
-
<MetricCellView cell={cell} attemptHref={attemptHref} />
|
|
134
|
+
<MetricCellView cell={cell} attemptHref={attemptHref} locale={locale} />
|
|
59
135
|
) : (
|
|
60
|
-
// 数据侧没给这个格子(理论上
|
|
136
|
+
// 数据侧没给这个格子(理论上 tableData 不会缺列)——按空处理,不编数
|
|
61
137
|
<span className="nre-empty" />
|
|
62
138
|
)}
|
|
63
139
|
</td>
|
|
64
140
|
);
|
|
65
141
|
})}
|
|
142
|
+
{hasVerdicts && (
|
|
143
|
+
<td className="nre-td nre-verdicts-cell" data-sort-value={row.meta?.verdicts?.passed ?? ""}>
|
|
144
|
+
{row.meta?.verdicts ? (
|
|
145
|
+
<VerdictTally verdicts={row.meta.verdicts} locale={locale} />
|
|
146
|
+
) : (
|
|
147
|
+
<span className="nre-missing">—</span>
|
|
148
|
+
)}
|
|
149
|
+
</td>
|
|
150
|
+
)}
|
|
66
151
|
</tr>
|
|
67
152
|
))}
|
|
68
153
|
</tbody>
|
|
69
154
|
</table>
|
|
70
155
|
);
|
|
156
|
+
|
|
157
|
+
if (!filter) return table;
|
|
158
|
+
// 过滤输入框渲染在表格前(同一个 wrap 里),enhance.js 经 data-nre-filter 接管;
|
|
159
|
+
// 无 JS 时静默无功能,表格内容依旧完整。
|
|
160
|
+
return (
|
|
161
|
+
<div className={cx("nre", "nre-metric-table-wrap", className)}>
|
|
162
|
+
<input
|
|
163
|
+
className="nre-filter"
|
|
164
|
+
data-nre-filter=""
|
|
165
|
+
type="search"
|
|
166
|
+
placeholder={localeText(locale, "table.filterPlaceholder")}
|
|
167
|
+
/>
|
|
168
|
+
{table}
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
71
171
|
}
|
|
@@ -1,69 +1,91 @@
|
|
|
1
1
|
// RunOverview:页头 KPI 条——「这批数据是什么」。
|
|
2
2
|
// 数字下面标注数据来源(几个快照、何时跑的);warnings 有内容时直接显示在条内,
|
|
3
|
-
// 诚实不靠使用者记得渲染(docs/reports.md
|
|
3
|
+
// 诚实不靠使用者记得渲染(docs/feature/reports/library.md「RunOverview」行为清单)。
|
|
4
4
|
|
|
5
5
|
import type { ReactElement } from "react";
|
|
6
6
|
import type { OverviewData } from "../types.ts";
|
|
7
|
-
import {
|
|
7
|
+
import { DEFAULT_REPORT_LOCALE, localeText, type ReportLocale } from "../locale.ts";
|
|
8
|
+
import { cx, formatDurationMs, formatUSD } from "./format.ts";
|
|
8
9
|
|
|
9
10
|
export function RunOverview({
|
|
10
11
|
data,
|
|
11
12
|
className,
|
|
13
|
+
locale = DEFAULT_REPORT_LOCALE,
|
|
12
14
|
}: {
|
|
13
15
|
data: OverviewData;
|
|
14
16
|
className?: string;
|
|
17
|
+
locale?: ReportLocale;
|
|
15
18
|
}): ReactElement {
|
|
16
19
|
const { totals } = data;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
const missing = <span className="nre-missing">{localeText(locale, "cell.missing")}</span>;
|
|
21
|
+
// 通过率只渲染 computeCell 算好的 MetricCell,不现场重算——覆盖率角标复用
|
|
22
|
+
// MetricCellView 的缺数据/coverage 语义(缺数据 = 显式文案,绝不画 0%)
|
|
23
|
+
const { passRate } = totals;
|
|
24
|
+
const passRateNode =
|
|
25
|
+
passRate.value === null ? (
|
|
26
|
+
missing
|
|
27
|
+
) : (
|
|
28
|
+
<>
|
|
29
|
+
{passRate.display}
|
|
30
|
+
{passRate.samples < passRate.total && (
|
|
31
|
+
<sup
|
|
32
|
+
className="nre-coverage"
|
|
33
|
+
title={localeText(locale, "cell.coverageTitle", { samples: passRate.samples, total: passRate.total })}
|
|
34
|
+
>
|
|
35
|
+
{passRate.samples}/{passRate.total}
|
|
36
|
+
</sup>
|
|
37
|
+
)}
|
|
38
|
+
</>
|
|
39
|
+
);
|
|
20
40
|
|
|
21
41
|
return (
|
|
22
42
|
<header className={cx("nre", "nre-overview", className)}>
|
|
23
43
|
<dl className="nre-kpis">
|
|
24
44
|
<div className="nre-kpi">
|
|
25
|
-
<dt>
|
|
45
|
+
<dt>{localeText(locale, "overview.snapshots")}</dt>
|
|
26
46
|
<dd>{data.snapshots.length}</dd>
|
|
27
47
|
</div>
|
|
28
48
|
<div className="nre-kpi">
|
|
29
|
-
<dt>
|
|
49
|
+
<dt>{localeText(locale, "overview.evals")}</dt>
|
|
30
50
|
<dd>{totals.evals}</dd>
|
|
31
51
|
</div>
|
|
32
52
|
<div className="nre-kpi">
|
|
33
|
-
<dt>attempts</dt>
|
|
53
|
+
<dt>{localeText(locale, "overview.attempts")}</dt>
|
|
34
54
|
<dd>{totals.attempts}</dd>
|
|
35
55
|
</div>
|
|
36
56
|
<div className="nre-kpi">
|
|
37
|
-
<dt>
|
|
38
|
-
<dd>{
|
|
57
|
+
<dt>{localeText(locale, "overview.passRate")}</dt>
|
|
58
|
+
<dd>{passRateNode}</dd>
|
|
39
59
|
</div>
|
|
40
60
|
<div className="nre-kpi">
|
|
41
|
-
<dt>
|
|
61
|
+
<dt>{localeText(locale, "overview.totalCost")}</dt>
|
|
42
62
|
{/* costUSD 全缺 = null:显示缺数据,不编 $0 */}
|
|
43
|
-
<dd>
|
|
44
|
-
{totals.costUSD === null ? (
|
|
45
|
-
<span className="nre-missing">{MISSING_TEXT}</span>
|
|
46
|
-
) : (
|
|
47
|
-
formatUSD(totals.costUSD)
|
|
48
|
-
)}
|
|
49
|
-
</dd>
|
|
63
|
+
<dd>{totals.costUSD === null ? missing : formatUSD(totals.costUSD)}</dd>
|
|
50
64
|
</div>
|
|
51
65
|
<div className="nre-kpi">
|
|
52
|
-
<dt>
|
|
66
|
+
<dt>{localeText(locale, "overview.totalDuration")}</dt>
|
|
53
67
|
<dd>{formatDurationMs(totals.durationMs)}</dd>
|
|
54
68
|
</div>
|
|
55
69
|
</dl>
|
|
56
70
|
|
|
57
|
-
<p className="nre-
|
|
58
|
-
<span className="nre-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
<span className="nre-
|
|
71
|
+
<p className="nre-verdicts">
|
|
72
|
+
<span className="nre-verdict nre-verdict-passed">
|
|
73
|
+
{localeText(locale, "verdict.passed")} {totals.passed}
|
|
74
|
+
</span>
|
|
75
|
+
<span className="nre-verdict nre-verdict-failed">
|
|
76
|
+
{localeText(locale, "verdict.failed")} {totals.failed}
|
|
77
|
+
</span>
|
|
78
|
+
<span className="nre-verdict nre-verdict-errored">
|
|
79
|
+
{localeText(locale, "verdict.errored")} {totals.errored}
|
|
80
|
+
</span>
|
|
81
|
+
<span className="nre-verdict nre-verdict-skipped">
|
|
82
|
+
{localeText(locale, "verdict.skipped")} {totals.skipped}
|
|
83
|
+
</span>
|
|
62
84
|
</p>
|
|
63
85
|
|
|
64
86
|
{/* 数据来源:哪些快照、何时跑的——报告的数字都从这里来 */}
|
|
65
87
|
<p className="nre-source">
|
|
66
|
-
|
|
88
|
+
{localeText(locale, "overview.source", { n: data.snapshots.length })}
|
|
67
89
|
{data.snapshots.map((s) => (
|
|
68
90
|
<span key={`${s.experimentId}@${s.startedAt}`} className="nre-source-snapshot">
|
|
69
91
|
{s.experimentId}({s.agent}
|
|
@@ -72,7 +94,7 @@ export function RunOverview({
|
|
|
72
94
|
))}
|
|
73
95
|
</p>
|
|
74
96
|
|
|
75
|
-
{/*
|
|
97
|
+
{/* Selection 的警告(残缺快照等)直接渲染在条内,不静默;结构化字段供程序判断,这里打 message */}
|
|
76
98
|
{data.warnings.length > 0 && (
|
|
77
99
|
<ul className="nre-warnings">
|
|
78
100
|
{data.warnings.map((w, i) => (
|