niceeval 0.6.2 → 0.7.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.
- package/INDEX.md +23 -23
- package/README.zh.md +6 -6
- package/dist/agents/types.d.ts +2 -2
- package/dist/i18n/zh-CN.d.ts +3 -3
- package/dist/report/aggregate.d.ts +32 -26
- package/dist/report/aggregate.js +157 -76
- package/dist/report/built-in/index.d.ts +2 -0
- package/dist/report/built-in/index.js +8 -0
- package/dist/report/components.d.ts +91 -164
- package/dist/report/components.js +377 -114
- package/dist/report/compute.d.ts +86 -73
- package/dist/report/compute.js +592 -432
- package/dist/report/flag.d.ts +28 -17
- package/dist/report/flag.js +86 -16
- package/dist/report/format.d.ts +11 -11
- package/dist/report/format.js +17 -15
- package/dist/report/index.d.ts +16 -17
- package/dist/report/index.js +20 -22
- package/dist/report/load.js +3 -2
- package/dist/report/locale.d.ts +49 -34
- package/dist/report/locale.js +106 -58
- package/dist/report/metrics.d.ts +10 -3
- package/dist/report/metrics.js +46 -12
- package/dist/report/primitives.d.ts +42 -15
- package/dist/report/primitives.js +135 -26
- package/dist/report/react/AttemptList.d.ts +10 -8
- package/dist/report/react/AttemptList.js +18 -10
- package/dist/report/react/DeltaTable.js +19 -18
- package/dist/report/react/EvalList.d.ts +3 -3
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentComparison.d.ts +4 -2
- package/dist/report/react/ExperimentComparison.js +5 -4
- package/dist/report/react/ExperimentList.d.ts +3 -3
- package/dist/report/react/ExperimentList.js +16 -15
- package/dist/report/react/MetricBars.js +5 -4
- package/dist/report/react/MetricLine.js +12 -5
- package/dist/report/react/MetricMatrix.js +1 -1
- package/dist/report/react/MetricScatter.js +54 -17
- package/dist/report/react/MetricTable.js +2 -12
- package/dist/report/react/ScopeSummary.d.ts +10 -0
- package/dist/report/react/ScopeSummary.js +17 -0
- package/dist/report/react/Scoreboard.js +6 -6
- package/dist/report/react/cell.js +2 -2
- package/dist/report/react/fixtures.d.ts +5 -9
- package/dist/report/react/fixtures.js +105 -149
- package/dist/report/react/index.d.ts +15 -5
- package/dist/report/react/index.js +18 -7
- package/dist/report/report.d.ts +137 -20
- package/dist/report/report.js +261 -34
- package/dist/report/text/faces.d.ts +17 -19
- package/dist/report/text/faces.js +225 -157
- package/dist/report/text/plot.js +1 -1
- package/dist/report/text/table.js +2 -2
- package/dist/report/tree.d.ts +90 -40
- package/dist/report/tree.js +252 -94
- package/dist/report/types.d.ts +245 -300
- package/dist/report/types.js +4 -3
- package/dist/report/web.d.ts +21 -5
- package/dist/report/web.js +42 -16
- package/dist/results/select.d.ts +38 -16
- package/dist/results/select.js +73 -25
- package/dist/results/types.d.ts +38 -14
- package/dist/shared/aggregate.d.ts +3 -2
- package/dist/shared/aggregate.js +5 -4
- package/docs-site/zh/README.md +44 -0
- package/docs-site/zh/examples/ai-agent-application.mdx +63 -0
- package/docs-site/zh/examples/coding-agent-extensions.mdx +57 -0
- package/docs-site/zh/examples/index.mdx +50 -0
- package/docs-site/zh/{concepts → explanation}/adapter.mdx +11 -11
- package/docs-site/zh/{concepts → explanation}/assert.mdx +7 -7
- package/docs-site/zh/{concepts → explanation}/drive.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/evals.mdx +4 -4
- package/docs-site/zh/{concepts → explanation}/experiment.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/hitl.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/judge.mdx +5 -5
- package/docs-site/zh/{concepts → explanation}/overview.mdx +5 -5
- package/docs-site/zh/{guides → explanation}/runner.mdx +1 -1
- package/docs-site/zh/{concepts → explanation}/tier.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/agent-feedback-loop.mdx +7 -7
- package/docs-site/zh/{guides → how-to}/authoring.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/connect-otel.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/connect-your-agent.mdx +18 -18
- package/docs-site/zh/{guides → how-to}/custom-reports.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/experiments.mdx +3 -3
- package/docs-site/zh/{guides → how-to}/publish-report.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/sandbox-agent.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/sandbox-providers.mdx +1 -1
- package/docs-site/zh/{guides → how-to}/viewing-results.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/write-experiment.mdx +3 -3
- package/docs-site/zh/{guides → how-to}/write-send.mdx +13 -13
- package/docs-site/zh/index.mdx +23 -25
- package/docs-site/zh/introduction.mdx +8 -8
- package/docs-site/zh/reference/builtin-agents.mdx +5 -5
- package/docs-site/zh/reference/capabilities.mdx +6 -6
- package/docs-site/zh/reference/cli.mdx +9 -7
- package/docs-site/zh/reference/define-agent.mdx +1 -1
- package/docs-site/zh/reference/events.mdx +3 -3
- package/docs-site/zh/{guides → reference}/official-adapters.mdx +7 -7
- package/docs-site/zh/{guides → reference}/report-components.mdx +5 -5
- package/docs-site/zh/{guides → reference}/results-data.mdx +5 -5
- package/docs-site/zh/{guides → troubleshooting}/debug-sandbox.mdx +2 -2
- package/docs-site/zh/{guides → troubleshooting}/debugging.mdx +4 -2
- package/docs-site/zh/{quickstart.mdx → tutorials/quickstart.mdx} +5 -17
- package/package.json +6 -2
- package/src/agents/index.ts +2 -2
- package/src/agents/openai-compat.ts +1 -1
- package/src/agents/streaming.ts +2 -2
- package/src/agents/types.ts +3 -3
- package/src/cli.ts +42 -23
- package/src/context/context.ts +1 -1
- package/src/context/session.test.ts +1 -1
- package/src/context/session.ts +1 -1
- package/src/i18n/en.ts +18 -16
- package/src/i18n/zh-CN.ts +16 -15
- package/src/report/aggregate.ts +175 -87
- package/src/report/built-in/index.tsx +9 -0
- package/src/report/components.tsx +625 -285
- package/src/report/compute.ts +717 -515
- package/src/report/dual-render.test.tsx +738 -1148
- package/src/report/flag.ts +97 -33
- package/src/report/format.ts +18 -22
- package/src/report/index.ts +113 -58
- package/src/report/load.ts +3 -2
- package/src/report/locale.ts +120 -69
- package/src/report/metrics.ts +42 -12
- package/src/report/primitives.tsx +190 -45
- package/src/report/react/AttemptList.tsx +32 -20
- package/src/report/react/DeltaTable.tsx +63 -45
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentComparison.tsx +12 -7
- package/src/report/react/ExperimentList.tsx +38 -26
- package/src/report/react/MetricBars.tsx +5 -4
- package/src/report/react/MetricLine.tsx +13 -8
- package/src/report/react/MetricMatrix.tsx +2 -2
- package/src/report/react/MetricScatter.tsx +74 -20
- package/src/report/react/MetricTable.tsx +4 -76
- package/src/report/react/ScopeSummary.tsx +86 -0
- package/src/report/react/Scoreboard.tsx +28 -10
- package/src/report/react/cell.tsx +2 -2
- package/src/report/react/enhance.js +57 -5
- package/src/report/react/fixtures.ts +109 -156
- package/src/report/react/index.tsx +24 -39
- package/src/report/react/render.test.tsx +139 -104
- package/src/report/react/styles.css +181 -91
- package/src/report/report.test.ts +761 -1031
- package/src/report/report.ts +425 -47
- package/src/report/text/faces.ts +257 -164
- package/src/report/text/plot.ts +1 -1
- package/src/report/text/table.ts +2 -2
- package/src/report/tree.ts +362 -104
- package/src/report/types.ts +257 -287
- package/src/report/web.ts +63 -20
- package/src/results/attempt-evidence.test.ts +4 -4
- package/src/results/attempt-evidence.ts +5 -5
- package/src/results/copy.ts +6 -6
- package/src/results/host-equivalence.test.ts +26 -14
- package/src/results/index.ts +10 -4
- package/src/results/open.ts +8 -4
- package/src/results/results.test.ts +4 -3
- package/src/results/select.ts +104 -34
- package/src/results/types.ts +36 -14
- package/src/runner/feedback/human.test.ts +1 -1
- package/src/runner/run.ts +1 -1
- package/src/sandbox/cli-commands.ts +2 -2
- package/src/scoring/judge.test.ts +1 -1
- package/src/shared/aggregate.ts +5 -4
- package/src/show/compose.ts +50 -67
- package/src/show/index.ts +107 -56
- package/src/show/render.ts +43 -27
- package/src/show/report-host.test.ts +188 -0
- package/src/show/report-host.ts +375 -0
- package/src/show/show.test.ts +86 -36
- package/src/view/app/App.test.tsx +69 -0
- package/src/view/app/App.tsx +144 -48
- package/src/view/app/components/AttemptModal.tsx +324 -63
- package/src/view/app/components/CodeView.tsx +10 -4
- package/src/view/app/i18n.ts +31 -17
- package/src/view/app/lib/artifact-url.ts +14 -3
- package/src/view/app/main.tsx +13 -8
- package/src/view/app/pages/{RunsPage.tsx → AttemptsPage.tsx} +6 -6
- package/src/view/app/types.ts +4 -1
- package/src/view/artifact-serving.test.ts +21 -1
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +6 -6
- package/src/view/data.test.ts +9 -3
- package/src/view/data.ts +145 -49
- package/src/view/index.ts +48 -44
- package/src/view/server.ts +35 -15
- package/src/view/shared/types.ts +34 -5
- package/src/view/styles.css +224 -0
- package/src/view/view-report.test.ts +161 -57
- package/dist/report/built-ins/experiment-comparison.d.ts +0 -39
- package/dist/report/built-ins/experiment-comparison.js +0 -119
- package/dist/report/built-ins/index.d.ts +0 -2
- package/dist/report/built-ins/index.js +0 -2
- package/dist/report/react/GroupSummary.d.ts +0 -8
- package/dist/report/react/GroupSummary.js +0 -8
- package/dist/report/react/RunOverview.d.ts +0 -8
- package/dist/report/react/RunOverview.js +0 -12
- package/docs-site/zh/example/ai-agent-application.mdx +0 -152
- package/docs-site/zh/example/claude-code-codex-plugin.mdx +0 -167
- package/docs-site/zh/example/claude-code-codex-skill.mdx +0 -152
- package/docs-site/zh/example/showcase.mdx +0 -39
- package/src/report/built-in-user-parity.test.tsx +0 -597
- package/src/report/built-ins/experiment-comparison.tsx +0 -179
- package/src/report/built-ins/index.ts +0 -7
- package/src/report/react/GroupSummary.tsx +0 -66
- package/src/report/react/RunOverview.tsx +0 -109
- /package/docs-site/zh/{example/tier1-ai-sdk-v7.mdx → examples/integrations/ai-sdk-v7.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-claude-sdk.mdx → examples/integrations/claude-sdk.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-codex-sdk.mdx → examples/integrations/codex-sdk.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-langgraph.mdx → examples/integrations/langgraph.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-pi-sdk.mdx → examples/integrations/pi-sdk.mdx} +0 -0
- /package/docs-site/zh/{guides → how-to}/ci-integration.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/dataset-fanout.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/fixtures.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/reporters.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/scoring-guide.mdx +0 -0
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
// ExperimentList:实体列表的第一级。web 面是一行一个 experiment
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// ExperimentList:实体列表的第一级。web 面是一行一个 experiment 的固定八列比较表
|
|
2
|
+
// (Experiment / Model / Agent / Avg duration / End-to-end pass rate / Tokens / Cost / Result),
|
|
3
|
+
// 每行用原生 <details> 展开到 Eval 与 Attempt locator。数据完全来自 experimentListData(),
|
|
4
|
+
// 组件不重算、不推断组边界。
|
|
4
5
|
|
|
5
6
|
import type { ReactElement } from "react";
|
|
6
7
|
import type { AttemptLocator } from "../../results/locator.ts";
|
|
7
8
|
import type { AttemptListItem, ExperimentListEvalRow, ExperimentListItem } from "../types.ts";
|
|
8
|
-
import {
|
|
9
|
+
import { experimentDisplayName } from "../format.ts";
|
|
9
10
|
import { DEFAULT_REPORT_LOCALE, localeText, type ReportLocale } from "../locale.ts";
|
|
10
|
-
import { AttemptLocatorBadge } from "./AttemptList.tsx";
|
|
11
|
+
import { AttemptLocatorBadge, failureSummaryText } from "./AttemptList.tsx";
|
|
11
12
|
import { MetricCellView } from "./cell.tsx";
|
|
12
13
|
import { colorClassForKey } from "./colors.ts";
|
|
13
14
|
import { cx, formatDurationMs, formatUSD, verdictMark } from "./format.ts";
|
|
14
15
|
|
|
16
|
+
const DEFAULT_ATTEMPT_HREF = (locator: AttemptLocator): string => `#/attempt/${locator}`;
|
|
17
|
+
|
|
15
18
|
const verdictOrder = ["passed", "failed", "errored", "skipped"] as const;
|
|
16
19
|
|
|
17
20
|
function passRateTone(value: number | null): string | undefined {
|
|
@@ -34,8 +37,8 @@ function formatDate(value: string, locale: ReportLocale): string {
|
|
|
34
37
|
|
|
35
38
|
function VerdictSummary({ item, locale }: { item: ExperimentListItem; locale: ReportLocale }): ReactElement {
|
|
36
39
|
const parts = verdictOrder
|
|
37
|
-
.filter((verdict) => item.
|
|
38
|
-
.map((verdict) => `${item.
|
|
40
|
+
.filter((verdict) => item.evalVerdicts[verdict] > 0)
|
|
41
|
+
.map((verdict) => `${item.evalVerdicts[verdict]} ${localeText(locale, `verdict.${verdict}`)}`);
|
|
39
42
|
return <span className="nre-experiment-pill">{parts.join(" / ") || "—"}</span>;
|
|
40
43
|
}
|
|
41
44
|
|
|
@@ -43,12 +46,14 @@ function ExperimentAttemptRow({
|
|
|
43
46
|
attempt,
|
|
44
47
|
last,
|
|
45
48
|
attemptHref,
|
|
49
|
+
locale,
|
|
46
50
|
}: {
|
|
47
51
|
attempt: AttemptListItem;
|
|
48
52
|
last: boolean;
|
|
49
53
|
attemptHref: (locator: AttemptLocator) => string;
|
|
54
|
+
locale: ReportLocale;
|
|
50
55
|
}): ReactElement {
|
|
51
|
-
const reason =
|
|
56
|
+
const reason = failureSummaryText(attempt, locale);
|
|
52
57
|
return (
|
|
53
58
|
<li className={cx("nre-experiment-attempt-row", `nre-eval-${attempt.verdict}`)}>
|
|
54
59
|
<span className="nre-attempt-branch" aria-hidden="true">{last ? "└─" : "├─"}</span>
|
|
@@ -57,8 +62,9 @@ function ExperimentAttemptRow({
|
|
|
57
62
|
</span>
|
|
58
63
|
<span className="nre-eval-attempt-metrics">
|
|
59
64
|
{formatDurationMs(attempt.durationMs)}
|
|
60
|
-
{attempt.costUSD !==
|
|
65
|
+
{attempt.costUSD !== null && <> · {formatUSD(attempt.costUSD)}</>}
|
|
61
66
|
</span>
|
|
67
|
+
{/* passed attempt 的 Result 是 —,不罗列通过的 assertions */}
|
|
62
68
|
<span className="nre-eval-reason">{reason ?? "—"}</span>
|
|
63
69
|
</li>
|
|
64
70
|
);
|
|
@@ -73,10 +79,11 @@ function EvalAttempts({
|
|
|
73
79
|
attemptHref: (locator: AttemptLocator) => string;
|
|
74
80
|
locale: ReportLocale;
|
|
75
81
|
}): ReactElement {
|
|
76
|
-
const duration = row.
|
|
77
|
-
const cost = row.
|
|
82
|
+
const duration = row.durationMs.value === null ? localeText(locale, "cell.missing") : formatDurationMs(row.durationMs.value);
|
|
83
|
+
const cost = row.costUSD.value === null ? localeText(locale, "cell.missing") : formatUSD(row.costUSD.value);
|
|
78
84
|
return (
|
|
79
85
|
<li className="nre-experiment-eval">
|
|
86
|
+
{/* Eval 父行只显示折叠判定、Attempt 数与题级平均;失败原因只在 Attempt 子行 */}
|
|
80
87
|
<div className={cx("nre-experiment-eval-header", `nre-eval-${row.verdict}`)}>
|
|
81
88
|
<span className={cx("nre-eval-verdict", `nre-verdict-${row.verdict}`)}>{verdictMark(row.verdict)}</span>
|
|
82
89
|
<span className="nre-eval-id">{row.evalId}</span>
|
|
@@ -94,6 +101,7 @@ function EvalAttempts({
|
|
|
94
101
|
attempt={attempt}
|
|
95
102
|
last={index === row.attempts.length - 1}
|
|
96
103
|
attemptHref={attemptHref}
|
|
104
|
+
locale={locale}
|
|
97
105
|
/>
|
|
98
106
|
))}
|
|
99
107
|
</ul>
|
|
@@ -107,7 +115,7 @@ function Flags({ flags, locale }: { flags: Record<string, unknown> | undefined;
|
|
|
107
115
|
<div className="nre-experiment-flags">
|
|
108
116
|
<span>{localeText(locale, "experimentList.flags")}</span>
|
|
109
117
|
{Object.entries(flags).map(([key, value]) => (
|
|
110
|
-
<b key={key}>{key}={
|
|
118
|
+
<b key={key}>{key}={typeof value === "string" ? value : JSON.stringify(value)}</b>
|
|
111
119
|
))}
|
|
112
120
|
</div>
|
|
113
121
|
);
|
|
@@ -127,6 +135,7 @@ function ExperimentRow({
|
|
|
127
135
|
return (
|
|
128
136
|
<details className="nre-experiment-entry">
|
|
129
137
|
<summary className="nre-experiment-summary">
|
|
138
|
+
{/* relativeTo 只影响显示末段;完整 id 仍是排序 / 着色 / 过滤 / 折叠的键 */}
|
|
130
139
|
<span className="nre-experiment-name" data-sort-value={item.experimentId}>
|
|
131
140
|
<b className={cx("nre-experiment-id", "nre-key", colorClassForKey(item.experimentId))}>
|
|
132
141
|
{experimentDisplayName(item.experimentId, relativeTo)}
|
|
@@ -139,19 +148,22 @@ function ExperimentRow({
|
|
|
139
148
|
</span>
|
|
140
149
|
<span data-sort-value={item.model ?? ""}>{item.model ?? localeText(locale, "experimentList.defaultModel")}</span>
|
|
141
150
|
<span data-sort-value={item.agent}>{item.agent}</span>
|
|
142
|
-
<span className="nre-num" data-sort-value={item.
|
|
143
|
-
<MetricCellView cell={item.
|
|
151
|
+
<span className="nre-num" data-sort-value={item.durationMs.value ?? ""}>
|
|
152
|
+
<MetricCellView cell={item.durationMs} locale={locale} />
|
|
144
153
|
</span>
|
|
145
|
-
<span
|
|
146
|
-
|
|
154
|
+
<span
|
|
155
|
+
className={cx("nre-num", passRateTone(item.endToEndPassRate.value))}
|
|
156
|
+
data-sort-value={item.endToEndPassRate.value ?? ""}
|
|
157
|
+
>
|
|
158
|
+
<MetricCellView cell={item.endToEndPassRate} locale={locale} />
|
|
147
159
|
</span>
|
|
148
160
|
<span className="nre-num" data-sort-value={item.tokens.value ?? ""}>
|
|
149
161
|
<MetricCellView cell={item.tokens} locale={locale} />
|
|
150
162
|
</span>
|
|
151
|
-
<span className="nre-num" data-sort-value={item.
|
|
152
|
-
<MetricCellView cell={item.
|
|
163
|
+
<span className="nre-num" data-sort-value={item.costUSD.value ?? ""}>
|
|
164
|
+
<MetricCellView cell={item.costUSD} locale={locale} />
|
|
153
165
|
</span>
|
|
154
|
-
<span data-sort-value={item.
|
|
166
|
+
<span data-sort-value={item.evalVerdicts.passed}><VerdictSummary item={item} locale={locale} /></span>
|
|
155
167
|
</summary>
|
|
156
168
|
<div className="nre-experiment-detail">
|
|
157
169
|
<Flags flags={item.flags} locale={locale} />
|
|
@@ -166,15 +178,15 @@ function ExperimentRow({
|
|
|
166
178
|
}
|
|
167
179
|
|
|
168
180
|
export function ExperimentList({
|
|
169
|
-
|
|
170
|
-
attemptHref,
|
|
181
|
+
data,
|
|
182
|
+
attemptHref = DEFAULT_ATTEMPT_HREF,
|
|
171
183
|
filter = false,
|
|
172
184
|
className,
|
|
173
185
|
locale = DEFAULT_REPORT_LOCALE,
|
|
174
186
|
relativeTo,
|
|
175
187
|
}: {
|
|
176
|
-
|
|
177
|
-
attemptHref
|
|
188
|
+
data: readonly ExperimentListItem[];
|
|
189
|
+
attemptHref?: (locator: AttemptLocator) => string;
|
|
178
190
|
filter?: boolean;
|
|
179
191
|
className?: string;
|
|
180
192
|
locale?: ReportLocale;
|
|
@@ -187,7 +199,7 @@ export function ExperimentList({
|
|
|
187
199
|
localeText(locale, "experimentList.avgDuration"),
|
|
188
200
|
localeText(locale, "experimentList.passRate"),
|
|
189
201
|
localeText(locale, "experimentList.tokens"),
|
|
190
|
-
localeText(locale, "experimentList.
|
|
202
|
+
localeText(locale, "experimentList.cost"),
|
|
191
203
|
localeText(locale, "experimentList.result"),
|
|
192
204
|
];
|
|
193
205
|
const board = (
|
|
@@ -204,8 +216,8 @@ export function ExperimentList({
|
|
|
204
216
|
</button>
|
|
205
217
|
))}
|
|
206
218
|
</div>
|
|
207
|
-
{
|
|
208
|
-
{
|
|
219
|
+
{data.length === 0 && <p className="nre-experiment-list-empty">{localeText(locale, "attemptList.empty")}</p>}
|
|
220
|
+
{data.map((item) => (
|
|
209
221
|
<ExperimentRow key={item.experimentId} item={item} attemptHref={attemptHref} locale={locale} relativeTo={relativeTo} />
|
|
210
222
|
))}
|
|
211
223
|
</div>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import type { ReactElement } from "react";
|
|
7
7
|
import type { MatrixData, MetricCell } from "../types.ts";
|
|
8
8
|
import type { AttemptLocator } from "../../results/locator.ts";
|
|
9
|
-
import { DEFAULT_REPORT_LOCALE, resolveMetricLabel, type ReportLocale } from "../locale.ts";
|
|
9
|
+
import { DEFAULT_REPORT_LOCALE, resolveLocalizedText, resolveMetricLabel, type ReportLocale } from "../locale.ts";
|
|
10
10
|
import { colorClassForKey, seriesClassForKey } from "./colors.ts";
|
|
11
11
|
import { cx } from "./format.ts";
|
|
12
12
|
|
|
@@ -52,7 +52,7 @@ export function MetricBars({
|
|
|
52
52
|
className="nre-bars-svg"
|
|
53
53
|
viewBox={`0 0 ${WIDTH} ${HEIGHT}`}
|
|
54
54
|
role="img"
|
|
55
|
-
aria-label={`${metricLabel} by ${data.
|
|
55
|
+
aria-label={`${metricLabel} by ${data.rowDimension} × ${data.columnDimension}`}
|
|
56
56
|
>
|
|
57
57
|
{groupKeys.map((group, gi) => {
|
|
58
58
|
const x0 = PLOT.left + gi * groupWidth;
|
|
@@ -71,7 +71,8 @@ export function MetricBars({
|
|
|
71
71
|
const h = Math.max(1, ratioOf(cell.value as number) * plotHeight);
|
|
72
72
|
const x = startX + si * barWidth;
|
|
73
73
|
const y = PLOT.bottom - h;
|
|
74
|
-
const
|
|
74
|
+
const display = resolveLocalizedText(cell.display, locale);
|
|
75
|
+
const title = `${group} · ${series}: ${display}(${cell.samples}/${cell.total})`;
|
|
75
76
|
const rect = (
|
|
76
77
|
<rect
|
|
77
78
|
className={cx("nre-bar", seriesClassForKey(series))}
|
|
@@ -89,7 +90,7 @@ export function MetricBars({
|
|
|
89
90
|
{attemptHref && locator ? <a href={attemptHref(locator)}>{rect}</a> : rect}
|
|
90
91
|
{/* 柱顶标数值;覆盖不全时把 samples/total 一并标出,不藏 */}
|
|
91
92
|
<text className="nre-bar-value" x={x + barWidth / 2} y={y - 4} textAnchor="middle">
|
|
92
|
-
{cell.samples < cell.total ? `${
|
|
93
|
+
{cell.samples < cell.total ? `${display} ${cell.samples}/${cell.total}` : display}
|
|
93
94
|
</text>
|
|
94
95
|
</g>
|
|
95
96
|
);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { ReactElement } from "react";
|
|
9
9
|
import type { LineData } from "../types.ts";
|
|
10
|
-
import { DEFAULT_REPORT_LOCALE, countText, localeText, resolveMetricLabel, type ReportLocale } from "../locale.ts";
|
|
10
|
+
import { DEFAULT_REPORT_LOCALE, countText, localeText, resolveLocalizedText, resolveMetricLabel, type ReportLocale } from "../locale.ts";
|
|
11
11
|
import { seriesClassForKey } from "./colors.ts";
|
|
12
12
|
import { cx } from "./format.ts";
|
|
13
13
|
|
|
@@ -52,6 +52,7 @@ export function MetricLine({
|
|
|
52
52
|
const missing = data.rows.filter((r) => r.x === null || r.y.value === null);
|
|
53
53
|
const drawableRows = data.rows.filter((r) => r.x !== null && r.y.value !== null);
|
|
54
54
|
const yLabel = resolveMetricLabel(data.y.label, locale, data.y.key);
|
|
55
|
+
const xLabel = resolveMetricLabel(data.x.label, locale, data.x.key);
|
|
55
56
|
|
|
56
57
|
const missingNote =
|
|
57
58
|
missing.length > 0 ? (
|
|
@@ -83,7 +84,7 @@ export function MetricLine({
|
|
|
83
84
|
series: r.series,
|
|
84
85
|
xValue: r.x as number,
|
|
85
86
|
yValue: r.y.value as number,
|
|
86
|
-
title: `${r.key}\n${
|
|
87
|
+
title: `${r.key}\n${xLabel}: ${resolveLocalizedText(r.xDisplay, locale)}\n${yLabel}: ${resolveLocalizedText(r.y.display, locale)}(${r.y.samples}/${r.y.total})`,
|
|
87
88
|
px: xScale.scale(r.x as number),
|
|
88
89
|
py: yScale.scale(r.y.value as number),
|
|
89
90
|
}));
|
|
@@ -103,10 +104,14 @@ export function MetricLine({
|
|
|
103
104
|
|
|
104
105
|
const xTicks = xScale.lo === xScale.hi ? [xScale.lo] : [xScale.lo, xScale.hi];
|
|
105
106
|
const yTicks = yScale.lo === yScale.hi ? [yScale.lo] : [yScale.lo, yScale.hi];
|
|
106
|
-
const xDisplayFor = (value: number) =>
|
|
107
|
-
drawableRows.find((r) => r.x === value)
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
const xDisplayFor = (value: number) => {
|
|
108
|
+
const row = drawableRows.find((r) => r.x === value);
|
|
109
|
+
return row === undefined ? String(value) : resolveLocalizedText(row.xDisplay, locale);
|
|
110
|
+
};
|
|
111
|
+
const yDisplayFor = (value: number) => {
|
|
112
|
+
const row = drawableRows.find((r) => r.y.value === value);
|
|
113
|
+
return row === undefined ? String(value) : resolveLocalizedText(row.y.display, locale);
|
|
114
|
+
};
|
|
110
115
|
|
|
111
116
|
return (
|
|
112
117
|
<figure className={cx("nre", "nre-metric-line", className)}>
|
|
@@ -114,7 +119,7 @@ export function MetricLine({
|
|
|
114
119
|
className="nre-line-svg"
|
|
115
120
|
viewBox={`0 0 ${WIDTH} ${HEIGHT}`}
|
|
116
121
|
role="img"
|
|
117
|
-
aria-label={`${yLabel} by ${
|
|
122
|
+
aria-label={`${yLabel} by ${xLabel}`}
|
|
118
123
|
>
|
|
119
124
|
<rect
|
|
120
125
|
className="nre-line-plot"
|
|
@@ -125,7 +130,7 @@ export function MetricLine({
|
|
|
125
130
|
fill="none"
|
|
126
131
|
/>
|
|
127
132
|
<text className="nre-line-xlabel" x={(PLOT.left + PLOT.right) / 2} y={HEIGHT - 8} textAnchor="middle">
|
|
128
|
-
{
|
|
133
|
+
{xLabel}
|
|
129
134
|
{data.x.unit ? `(${data.x.unit})` : ""}
|
|
130
135
|
</text>
|
|
131
136
|
<text
|
|
@@ -40,13 +40,13 @@ export function MetricMatrix({
|
|
|
40
40
|
{resolveMetricLabel(data.metric.label, locale, data.metric.key)}
|
|
41
41
|
{data.metric.unit && <span className="nre-unit">({data.metric.unit})</span>}
|
|
42
42
|
<span className="nre-matrix-axes">
|
|
43
|
-
{data.
|
|
43
|
+
{data.rowDimension} × {data.columnDimension}
|
|
44
44
|
</span>
|
|
45
45
|
</caption>
|
|
46
46
|
<thead>
|
|
47
47
|
<tr>
|
|
48
48
|
<th scope="col" className="nre-dimension">
|
|
49
|
-
{data.
|
|
49
|
+
{data.rowDimension}
|
|
50
50
|
</th>
|
|
51
51
|
{columnKeys.map((column) => (
|
|
52
52
|
// 列键 = 维度键(如 agent):稳定散列上色,与 scatter 的线、DeltaTable 的行同色
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// MetricScatter:质量 × 成本 frontier 的积木,内联 SVG、零图表库、零 hooks。
|
|
2
|
-
// 轴向随 better:"lower"
|
|
2
|
+
// 轴向随 better:"lower" 的轴反向画(值大在左/下),「更好」恒指向右上;方向提示恒为
|
|
3
|
+
// 「越靠右上越好」且仅当两轴都声明 better 时显示;刻度显示真实值。
|
|
3
4
|
// niceTicks 刻度 + 网格线;每个点直接标注(placePointLabels 候选位择优:避开其它标签、
|
|
4
5
|
// 数据点与画布边界,离开左右紧邻位时补 leader line);
|
|
5
6
|
// 同系列的点按 x 值排序连线,系列图例列在图下。x 或 y 为 null 的点不画,
|
|
@@ -10,7 +11,7 @@
|
|
|
10
11
|
import type { ReactElement } from "react";
|
|
11
12
|
import type { MetricColumn, ScatterData } from "../types.ts";
|
|
12
13
|
import { formatMetricValue } from "../format.ts";
|
|
13
|
-
import { DEFAULT_REPORT_LOCALE, countText, localeText, resolveMetricLabel, type ReportLocale } from "../locale.ts";
|
|
14
|
+
import { DEFAULT_REPORT_LOCALE, countText, localeText, resolveLocalizedText, resolveMetricLabel, type ReportLocale } from "../locale.ts";
|
|
14
15
|
import { niceTicks, placePointLabels } from "./chart-math.ts";
|
|
15
16
|
import { colorClassForKey, seriesClassForKey } from "./colors.ts";
|
|
16
17
|
import { cx } from "./format.ts";
|
|
@@ -33,22 +34,54 @@ interface DrawablePoint {
|
|
|
33
34
|
py: number;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
/**
|
|
38
|
+
* 点的直接标签:末段在当前 data 中唯一才缩成末段;重名时逐步加长为能区分它们的最短
|
|
39
|
+
* 路径后缀(完整 id 与两轴值仍进 <title>)。
|
|
40
|
+
*/
|
|
41
|
+
function pointLabels(keys: readonly string[]): Map<string, string> {
|
|
42
|
+
const segsOf = (key: string) => key.split("/").filter(Boolean);
|
|
43
|
+
const depth = new Map<string, number>(keys.map((key) => [key, 1]));
|
|
44
|
+
for (;;) {
|
|
45
|
+
const byLabel = new Map<string, string[]>();
|
|
46
|
+
for (const key of keys) {
|
|
47
|
+
const segs = segsOf(key);
|
|
48
|
+
const label = segs.slice(-Math.min(depth.get(key)!, segs.length)).join("/") || key;
|
|
49
|
+
byLabel.set(label, [...(byLabel.get(label) ?? []), key]);
|
|
50
|
+
}
|
|
51
|
+
let grew = false;
|
|
52
|
+
for (const group of byLabel.values()) {
|
|
53
|
+
if (group.length < 2) continue;
|
|
54
|
+
for (const key of group) {
|
|
55
|
+
const segs = segsOf(key);
|
|
56
|
+
if (depth.get(key)! < segs.length) {
|
|
57
|
+
depth.set(key, depth.get(key)! + 1);
|
|
58
|
+
grew = true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!grew) {
|
|
63
|
+
const out = new Map<string, string>();
|
|
64
|
+
for (const key of keys) {
|
|
65
|
+
const segs = segsOf(key);
|
|
66
|
+
out.set(key, segs.slice(-Math.min(depth.get(key)!, segs.length)).join("/") || key);
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
39
71
|
}
|
|
40
72
|
|
|
41
73
|
/**
|
|
42
|
-
* 一根轴:niceTicks 撑出整齐的值域,值 →
|
|
43
|
-
* better: "lower"
|
|
74
|
+
* 一根轴:niceTicks 撑出整齐的值域,值 → 像素做线性映射。轴方向跟随指标的 `better`:
|
|
75
|
+
* `better: "lower"` 的轴反向渲染(值大的一端在左 / 下),「更好」因此恒指向右与上;
|
|
76
|
+
* 刻度标签始终显示真实值,反向只改方向不改数字(docs/feature/reports/library/metric-views.md)。
|
|
44
77
|
*/
|
|
45
|
-
function axisScale(values: number[],
|
|
78
|
+
function axisScale(values: number[], pixelLo: number, pixelHi: number, invert: boolean) {
|
|
46
79
|
const ticks = niceTicks(Math.min(...values), Math.max(...values), 5);
|
|
47
80
|
const lo = ticks[0];
|
|
48
81
|
const hi = ticks[ticks.length - 1];
|
|
49
82
|
const scale = (v: number) => {
|
|
50
83
|
let t = (v - lo) / (hi - lo || 1);
|
|
51
|
-
if (
|
|
84
|
+
if (invert) t = 1 - t;
|
|
52
85
|
return pixelLo + t * (pixelHi - pixelLo);
|
|
53
86
|
};
|
|
54
87
|
return { ticks, scale };
|
|
@@ -91,9 +124,24 @@ export function MetricScatter({
|
|
|
91
124
|
|
|
92
125
|
const axisLabel = (label: string, col: MetricColumn) => `${label}${col.unit ? `(${col.unit})` : ""}`;
|
|
93
126
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
127
|
+
// 轴方向跟随 better:lower 反向(值大在左 / 下),higher 与未声明正向。
|
|
128
|
+
const xScale = axisScale(
|
|
129
|
+
drawableRows.map((r) => r.x.value as number),
|
|
130
|
+
MARGIN.left,
|
|
131
|
+
MARGIN.left + PLOT_W,
|
|
132
|
+
data.x.better === "lower",
|
|
133
|
+
);
|
|
134
|
+
// y 像素轴向下增长:正向 = 高值在上 → 映射到 [bottom, top];lower 反向 = 高值在下。
|
|
135
|
+
const yScale = axisScale(
|
|
136
|
+
drawableRows.map((r) => r.y.value as number),
|
|
137
|
+
MARGIN.top + PLOT_H,
|
|
138
|
+
MARGIN.top,
|
|
139
|
+
data.y.better === "lower",
|
|
140
|
+
);
|
|
141
|
+
const labelByKey = pointLabels(drawableRows.map((r) => r.key));
|
|
142
|
+
// 方向提示恒为「越靠右上越好」,仅当两轴都声明 better 时显示——任一轴未声明,
|
|
143
|
+
// 组件不猜「更好」朝哪边,整图无提示。
|
|
144
|
+
const showBetterHint = data.x.better !== undefined && data.y.better !== undefined;
|
|
97
145
|
|
|
98
146
|
const points: DrawablePoint[] = drawableRows.map((r) => {
|
|
99
147
|
const xValue = r.x.value as number;
|
|
@@ -101,12 +149,11 @@ export function MetricScatter({
|
|
|
101
149
|
return {
|
|
102
150
|
key: r.key,
|
|
103
151
|
series: r.series,
|
|
104
|
-
label:
|
|
152
|
+
label: labelByKey.get(r.key) ?? r.key,
|
|
105
153
|
xValue,
|
|
106
154
|
yValue,
|
|
107
|
-
// hover 内容:experiment(点键)+ 系列(
|
|
108
|
-
|
|
109
|
-
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})`,
|
|
155
|
+
// hover 内容:experiment(点键)+ 系列(有则加一行)+ 两轴 display 与 samples/total
|
|
156
|
+
title: `${r.key}${r.series !== undefined ? `\n${r.series}` : ""}\n${xLabel}: ${resolveLocalizedText(r.x.display, locale)}(${r.x.samples}/${r.x.total})\n${yLabel}: ${resolveLocalizedText(r.y.display, locale)}(${r.y.samples}/${r.y.total})`,
|
|
110
157
|
px: xScale.scale(xValue),
|
|
111
158
|
py: yScale.scale(yValue),
|
|
112
159
|
};
|
|
@@ -149,10 +196,17 @@ export function MetricScatter({
|
|
|
149
196
|
))}
|
|
150
197
|
</g>
|
|
151
198
|
|
|
152
|
-
{/*
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
199
|
+
{/* 轴向已随 better 反正,「更好」恒指向右上;提示只在两轴都声明 better 时出现 */}
|
|
200
|
+
{showBetterHint && (
|
|
201
|
+
<text
|
|
202
|
+
className="nre-scatter-better-hint"
|
|
203
|
+
x={MARGIN.left + PLOT_W - 6}
|
|
204
|
+
y={MARGIN.top + 14}
|
|
205
|
+
textAnchor="end"
|
|
206
|
+
>
|
|
207
|
+
{localeText(locale, "scatter.betterUpperRight")}
|
|
208
|
+
</text>
|
|
209
|
+
)}
|
|
156
210
|
|
|
157
211
|
{/* 刻度:已格式化的整齐值(formatMetricValue 与计算侧同一套) */}
|
|
158
212
|
<g className="nre-scatter-axis nre-scatter-axis-y">
|
|
@@ -1,43 +1,20 @@
|
|
|
1
1
|
// MetricTable:行维度 × 指标列。没有实体下钻——要展开到 experiment 的 Eval 或 Eval 的
|
|
2
2
|
// Attempt,用 ExperimentList / EvalList,这个组件只表达任意维度 × 任意指标。
|
|
3
|
-
// 行按传入顺序渲染——排序发生在计算侧(
|
|
3
|
+
// 行按传入顺序渲染——排序发生在计算侧(metricTableData 的 sort 参数),组件不重排;
|
|
4
4
|
// 静态 HTML 以数据侧预排的顺序呈现即完整。web 面额外输出渐进增强的 data 属性:
|
|
5
5
|
// 所有表头带 data-nre-sort、格子带 data-sort-value,enhance.js 在场时点表头
|
|
6
6
|
// 可就地重排(纯展示态交互,不改口径);filter 开时在表格前渲染过滤输入框。
|
|
7
|
-
// meta 在场(rows: "experiment")时补 Model / Agent / Verdicts 列,列序对齐 view
|
|
8
|
-
// 原生榜单:experiment、model、agent、指标列…、verdicts。
|
|
9
7
|
// 列头以箭头标注 better 方向;samples < total 的格子带覆盖率角标;
|
|
10
8
|
// 一组全 null 渲染成「缺数据」,绝不画 0(逻辑在 MetricCellView)。
|
|
11
9
|
|
|
12
10
|
import type { ReactElement } from "react";
|
|
13
|
-
import type { TableData
|
|
11
|
+
import type { TableData } from "../types.ts";
|
|
14
12
|
import type { AttemptLocator } from "../../results/locator.ts";
|
|
15
13
|
import { DEFAULT_REPORT_LOCALE, localeText, resolveMetricLabel, type ReportLocale } from "../locale.ts";
|
|
16
14
|
import { MetricCellView } from "./cell.tsx";
|
|
17
15
|
import { colorClassForKey } from "./colors.ts";
|
|
18
16
|
import { cx } from "./format.ts";
|
|
19
17
|
|
|
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
|
-
|
|
41
18
|
export function MetricTable({
|
|
42
19
|
data,
|
|
43
20
|
attemptHref,
|
|
@@ -51,27 +28,13 @@ export function MetricTable({
|
|
|
51
28
|
className?: string;
|
|
52
29
|
locale?: ReportLocale;
|
|
53
30
|
}): ReactElement {
|
|
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
31
|
const table = (
|
|
59
32
|
<table className={cx("nre", "nre-metric-table", !filter && className)}>
|
|
60
33
|
<thead>
|
|
61
34
|
<tr>
|
|
62
35
|
<th scope="col" className="nre-dimension" data-nre-sort="">
|
|
63
|
-
{data.
|
|
36
|
+
{data.rowDimension}
|
|
64
37
|
</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
|
-
)}
|
|
75
38
|
{data.columns.map((col) => (
|
|
76
39
|
<th scope="col" key={col.key} className="nre-metric-col" data-nre-sort="">
|
|
77
40
|
{resolveMetricLabel(col.label, locale, col.key)}
|
|
@@ -87,11 +50,6 @@ export function MetricTable({
|
|
|
87
50
|
)}
|
|
88
51
|
</th>
|
|
89
52
|
))}
|
|
90
|
-
{hasVerdicts && (
|
|
91
|
-
<th scope="col" className="nre-verdicts-col" data-nre-sort="">
|
|
92
|
-
{localeText(locale, "table.verdicts")}
|
|
93
|
-
</th>
|
|
94
|
-
)}
|
|
95
53
|
</tr>
|
|
96
54
|
</thead>
|
|
97
55
|
<tbody>
|
|
@@ -104,28 +62,7 @@ export function MetricTable({
|
|
|
104
62
|
data-sort-value={row.key}
|
|
105
63
|
>
|
|
106
64
|
{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
|
-
)}
|
|
118
65
|
</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
|
-
)}
|
|
129
66
|
{data.columns.map((col) => {
|
|
130
67
|
const cell = row.cells[col.key];
|
|
131
68
|
return (
|
|
@@ -133,21 +70,12 @@ export function MetricTable({
|
|
|
133
70
|
{cell ? (
|
|
134
71
|
<MetricCellView cell={cell} attemptHref={attemptHref} locale={locale} />
|
|
135
72
|
) : (
|
|
136
|
-
// 数据侧没给这个格子(理论上
|
|
73
|
+
// 数据侧没给这个格子(理论上 metricTableData 不会缺列)——按空处理,不编数
|
|
137
74
|
<span className="nre-empty" />
|
|
138
75
|
)}
|
|
139
76
|
</td>
|
|
140
77
|
);
|
|
141
78
|
})}
|
|
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
|
-
)}
|
|
151
79
|
</tr>
|
|
152
80
|
))}
|
|
153
81
|
</tbody>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// ScopeSummary:一个范围的摘要卡——快照时间窗、experiment / eval / attempt 数、判定计票、
|
|
2
|
+
// 端到端成功率与总成本。data 恒携带 eval 级与 attempt 级两份计票;呈现 prop `votes`
|
|
3
|
+
// 只选择显示哪一级(默认 "eval"),不改变 data。成功率与总成本只渲染算好的 MetricCell,
|
|
4
|
+
// 不现场重算(docs/feature/reports/library/summaries.md「ScopeSummary」)。
|
|
5
|
+
|
|
6
|
+
import type { ReactElement } from "react";
|
|
7
|
+
import type { ScopeSummaryData, VerdictTally } from "../types.ts";
|
|
8
|
+
import { DEFAULT_REPORT_LOCALE, localeText, type ReportLocale } from "../locale.ts";
|
|
9
|
+
import { MetricCellView } from "./cell.tsx";
|
|
10
|
+
import { cx } from "./format.ts";
|
|
11
|
+
|
|
12
|
+
function VerdictTallyView({ tally, locale }: { tally: VerdictTally; locale: ReportLocale }): ReactElement {
|
|
13
|
+
const kinds = (["passed", "failed", "errored", "skipped"] as const).filter((k) => tally[k] > 0);
|
|
14
|
+
return (
|
|
15
|
+
<span className="nre-verdict-tally">
|
|
16
|
+
{kinds.map((kind) => (
|
|
17
|
+
<span key={kind} className={cx("nre-verdict-pill", `nre-verdict-${kind}`)}>
|
|
18
|
+
{tally[kind]} {localeText(locale, `verdict.${kind}`)}
|
|
19
|
+
</span>
|
|
20
|
+
))}
|
|
21
|
+
{kinds.length === 0 && <span className="nre-missing">—</span>}
|
|
22
|
+
</span>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function ScopeSummary({
|
|
27
|
+
data,
|
|
28
|
+
votes = "eval",
|
|
29
|
+
className,
|
|
30
|
+
locale = DEFAULT_REPORT_LOCALE,
|
|
31
|
+
}: {
|
|
32
|
+
data: ScopeSummaryData;
|
|
33
|
+
/** 显示哪一级计票;默认 "eval"。data 恒携带两级,votes 只选择呈现。 */
|
|
34
|
+
votes?: "eval" | "attempt";
|
|
35
|
+
className?: string;
|
|
36
|
+
locale?: ReportLocale;
|
|
37
|
+
}): ReactElement {
|
|
38
|
+
const tally = votes === "attempt" ? data.attemptVerdicts : data.evalVerdicts;
|
|
39
|
+
return (
|
|
40
|
+
<div className={cx("nre", "nre-scope-summary", className)} data-votes={votes}>
|
|
41
|
+
<dl className="nre-scope-kpis">
|
|
42
|
+
<div className="nre-scope-kpi nre-scope-kpi-rate">
|
|
43
|
+
<dt>{localeText(locale, "scopeSummary.passRate")}</dt>
|
|
44
|
+
<dd>
|
|
45
|
+
<MetricCellView cell={data.endToEndPassRate} locale={locale} />
|
|
46
|
+
</dd>
|
|
47
|
+
</div>
|
|
48
|
+
<div className="nre-scope-kpi">
|
|
49
|
+
<dt>{localeText(locale, "scopeSummary.experiments")}</dt>
|
|
50
|
+
<dd>{data.experiments}</dd>
|
|
51
|
+
</div>
|
|
52
|
+
<div className="nre-scope-kpi">
|
|
53
|
+
<dt>{localeText(locale, "scopeSummary.evals")}</dt>
|
|
54
|
+
<dd>{data.evals}</dd>
|
|
55
|
+
</div>
|
|
56
|
+
<div className="nre-scope-kpi">
|
|
57
|
+
<dt>{localeText(locale, "scopeSummary.attempts")}</dt>
|
|
58
|
+
<dd>{data.attempts}</dd>
|
|
59
|
+
</div>
|
|
60
|
+
<div className="nre-scope-kpi nre-scope-kpi-verdicts">
|
|
61
|
+
<dt>{localeText(locale, votes === "attempt" ? "scopeSummary.votesAttempt" : "scopeSummary.votesEval")}</dt>
|
|
62
|
+
<dd>
|
|
63
|
+
<VerdictTallyView tally={tally} locale={locale} />
|
|
64
|
+
</dd>
|
|
65
|
+
</div>
|
|
66
|
+
<div className="nre-scope-kpi">
|
|
67
|
+
<dt>{localeText(locale, "scopeSummary.totalCost")}</dt>
|
|
68
|
+
<dd>
|
|
69
|
+
<MetricCellView cell={data.totalCostUSD} locale={locale} />
|
|
70
|
+
</dd>
|
|
71
|
+
</div>
|
|
72
|
+
</dl>
|
|
73
|
+
{/* 时间窗:贡献当前数据的快照 startedAt 范围;空范围不编造当前时间 */}
|
|
74
|
+
{data.range.latestStartedAt !== null && (
|
|
75
|
+
<p className="nre-scope-summary-range">
|
|
76
|
+
{data.range.earliestStartedAt !== null && data.range.earliestStartedAt !== data.range.latestStartedAt
|
|
77
|
+
? localeText(locale, "scopeSummary.range", {
|
|
78
|
+
from: data.range.earliestStartedAt,
|
|
79
|
+
to: data.range.latestStartedAt,
|
|
80
|
+
})
|
|
81
|
+
: data.range.latestStartedAt}
|
|
82
|
+
</p>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|