niceeval 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +9 -4
- 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 +3 -1
- 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 -0
- 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 +4 -4
- 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 +10 -5
- package/docs-site/zh/guides/sandbox-providers.mdx +102 -0
- package/docs-site/zh/guides/viewing-results.mdx +199 -79
- package/docs-site/zh/guides/write-experiment.mdx +23 -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 +24 -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 +134 -43
- 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 +11 -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 +62 -2
- package/src/cli.ts +74 -47
- package/src/context/context.test.ts +20 -2
- package/src/context/context.ts +3 -0
- package/src/context/control-flow.ts +1 -1
- package/src/context/session.test.ts +92 -1
- package/src/context/session.ts +26 -1
- package/src/context/types.ts +4 -4
- package/src/define.test.ts +82 -0
- package/src/define.ts +72 -11
- package/src/expect/index.ts +5 -5
- package/src/i18n/en.ts +36 -15
- package/src/i18n/zh-CN.ts +39 -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 +97 -15
- 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 +69 -28
- 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/docs-site/zh/guides/sandbox-backends.mdx +0 -75
- 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
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// AttemptList:实体列表的叶子层——每项一个 Attempt,固定展示判定、断言、error、Judge 评语
|
|
2
|
+
// (assertions 的 detail/evidence)与证据引用(locator + 证据能力标记)。它不预设只看失败,
|
|
3
|
+
// 报告作者过滤 items、用 .slice() 限量,total 让渲染面如实报告剩余数量,不静默截断。
|
|
4
|
+
// ExperimentList / EvalList 的下钻数组是同一个 AttemptListItem[],这里的渲染逻辑因此
|
|
5
|
+
// 也是它们展开区里"逐条 attempt"那一层的唯一实现(通过 AttemptRow 导出复用,不重写一遍)。
|
|
6
|
+
|
|
7
|
+
import type { ReactElement } from "react";
|
|
8
|
+
import type { AttemptListItem } from "../types.ts";
|
|
9
|
+
import type { AttemptLocator } from "../../results/locator.ts";
|
|
10
|
+
import { DEFAULT_REPORT_LOCALE, localeText, type ReportLocale } from "../locale.ts";
|
|
11
|
+
import { colorClassForKey } from "./colors.ts";
|
|
12
|
+
import { capabilityBadge, cx, formatDurationMs, formatUSD, verdictMark } from "./format.ts";
|
|
13
|
+
|
|
14
|
+
/** locator + 判定符 + 证据能力标记的普通 <a>,AttemptList/EvalList/ExperimentList 共用。 */
|
|
15
|
+
export function AttemptLocatorBadge({
|
|
16
|
+
item,
|
|
17
|
+
attemptHref,
|
|
18
|
+
}: {
|
|
19
|
+
item: Pick<AttemptListItem, "locator" | "verdict" | "capabilities">;
|
|
20
|
+
attemptHref: (locator: AttemptLocator) => string;
|
|
21
|
+
}): ReactElement {
|
|
22
|
+
const caps = capabilityBadge(item.capabilities);
|
|
23
|
+
return (
|
|
24
|
+
<a
|
|
25
|
+
className={cx("nre-locator", `nre-verdict-${item.verdict}`)}
|
|
26
|
+
href={attemptHref(item.locator)}
|
|
27
|
+
>
|
|
28
|
+
{item.locator}
|
|
29
|
+
<span className="nre-locator-mark">{verdictMark(item.verdict)}</span>
|
|
30
|
+
{caps && <span className="nre-locator-caps">{caps}</span>}
|
|
31
|
+
</a>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function AssertionRow({ assertion, locale }: { assertion: AttemptListItem["assertions"][number]; locale: ReportLocale }): ReactElement {
|
|
36
|
+
return (
|
|
37
|
+
<li className={cx("nre-assertion", `nre-assertion-${assertion.severity}`, !assertion.passed && "nre-assertion-failed")}>
|
|
38
|
+
<span className="nre-assertion-severity">{assertion.severity}</span>
|
|
39
|
+
<span className="nre-assertion-name">{assertion.name}</span>
|
|
40
|
+
<span className="nre-assertion-score">
|
|
41
|
+
{localeText(locale, "attemptList.score", { score: assertion.score })}
|
|
42
|
+
</span>
|
|
43
|
+
{(assertion.detail || assertion.evidence) && (
|
|
44
|
+
<details className="nre-assertion-more">
|
|
45
|
+
<summary>{localeText(locale, "attemptList.details")}</summary>
|
|
46
|
+
{assertion.detail && <p className="nre-assertion-detail">{assertion.detail}</p>}
|
|
47
|
+
{assertion.evidence && <blockquote className="nre-assertion-evidence">{assertion.evidence}</blockquote>}
|
|
48
|
+
</details>
|
|
49
|
+
)}
|
|
50
|
+
</li>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 一条 Attempt 的完整卡片:AttemptList 自己的一项,也是 EvalList 展开区里的一行。 */
|
|
55
|
+
export function AttemptRow({
|
|
56
|
+
item,
|
|
57
|
+
attemptHref,
|
|
58
|
+
locale = DEFAULT_REPORT_LOCALE,
|
|
59
|
+
}: {
|
|
60
|
+
item: AttemptListItem;
|
|
61
|
+
attemptHref: (locator: AttemptLocator) => string;
|
|
62
|
+
locale?: ReportLocale;
|
|
63
|
+
}): ReactElement {
|
|
64
|
+
return (
|
|
65
|
+
<li className={cx("nre-attempt", `nre-attempt-${item.verdict}`)}>
|
|
66
|
+
<div className="nre-attempt-head">
|
|
67
|
+
<AttemptLocatorBadge item={item} attemptHref={attemptHref} />
|
|
68
|
+
<span className="nre-attempt-eval">{item.evalId}</span>
|
|
69
|
+
<span className="nre-attempt-experiment">{item.experimentId}</span>
|
|
70
|
+
{/* agent 键:稳定散列上色,与其它块(MetricMatrix 列头、DeltaTable 行…)同键同色 */}
|
|
71
|
+
<span className={cx("nre-attempt-agent", "nre-key", colorClassForKey(item.agent))}>{item.agent}</span>
|
|
72
|
+
<span className="nre-attempt-duration">{formatDurationMs(item.durationMs)}</span>
|
|
73
|
+
{item.costUSD !== undefined && <span className="nre-attempt-cost">{formatUSD(item.costUSD)}</span>}
|
|
74
|
+
</div>
|
|
75
|
+
{item.error && <p className="nre-attempt-error">{item.error}</p>}
|
|
76
|
+
{item.assertions.length > 0 && (
|
|
77
|
+
<ul className="nre-assertions">
|
|
78
|
+
{item.assertions.map((a, i) => (
|
|
79
|
+
<AssertionRow key={i} assertion={a} locale={locale} />
|
|
80
|
+
))}
|
|
81
|
+
</ul>
|
|
82
|
+
)}
|
|
83
|
+
</li>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function AttemptList({
|
|
88
|
+
items,
|
|
89
|
+
total,
|
|
90
|
+
attemptHref,
|
|
91
|
+
className,
|
|
92
|
+
locale = DEFAULT_REPORT_LOCALE,
|
|
93
|
+
}: {
|
|
94
|
+
items: AttemptListItem[];
|
|
95
|
+
/** items 被 slice 时的原始数量;如实显示还剩多少条没展示。 */
|
|
96
|
+
total?: number;
|
|
97
|
+
attemptHref: (locator: AttemptLocator) => string;
|
|
98
|
+
className?: string;
|
|
99
|
+
locale?: ReportLocale;
|
|
100
|
+
}): ReactElement {
|
|
101
|
+
const remaining = (total ?? items.length) - items.length;
|
|
102
|
+
return (
|
|
103
|
+
<section className={cx("nre", "nre-attempt-list", className)}>
|
|
104
|
+
{items.length === 0 && <p className="nre-attempt-list-empty">{localeText(locale, "attemptList.empty")}</p>}
|
|
105
|
+
<ul className="nre-attempts">
|
|
106
|
+
{items.map((item) => (
|
|
107
|
+
<AttemptRow key={item.locator} item={item} attemptHref={attemptHref} locale={locale} />
|
|
108
|
+
))}
|
|
109
|
+
</ul>
|
|
110
|
+
{remaining > 0 && (
|
|
111
|
+
<p className="nre-truncated">{localeText(locale, "attemptList.truncated", { n: remaining })}</p>
|
|
112
|
+
)}
|
|
113
|
+
</section>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import type { ReactElement } from "react";
|
|
6
6
|
import type { DeltaData, MetricColumn } from "../types.ts";
|
|
7
|
+
import { DEFAULT_REPORT_LOCALE, localeText, resolveMetricLabel, type ReportLocale } from "../locale.ts";
|
|
7
8
|
import { MetricCellView } from "./cell.tsx";
|
|
8
9
|
import { colorClassForKey } from "./colors.ts";
|
|
9
|
-
import {
|
|
10
|
+
import { cx } from "./format.ts";
|
|
10
11
|
|
|
11
12
|
/** Δ 的语义配色 class:好/坏由 better 方向判定,不看正负号本身。 */
|
|
12
13
|
function deltaToneClass(delta: number | null, better: MetricColumn["better"]): string {
|
|
@@ -20,20 +21,22 @@ function deltaToneClass(delta: number | null, better: MetricColumn["better"]): s
|
|
|
20
21
|
export function DeltaTable({
|
|
21
22
|
data,
|
|
22
23
|
className,
|
|
24
|
+
locale = DEFAULT_REPORT_LOCALE,
|
|
23
25
|
}: {
|
|
24
26
|
data: DeltaData;
|
|
25
27
|
className?: string;
|
|
28
|
+
locale?: ReportLocale;
|
|
26
29
|
}): ReactElement {
|
|
27
30
|
return (
|
|
28
31
|
<table className={cx("nre", "nre-delta-table", className)}>
|
|
29
32
|
<thead>
|
|
30
33
|
<tr>
|
|
31
34
|
<th scope="col" className="nre-dimension">
|
|
32
|
-
|
|
35
|
+
{localeText(locale, "delta.pairHeader")}
|
|
33
36
|
</th>
|
|
34
37
|
{data.columns.map((col) => (
|
|
35
38
|
<th scope="col" key={col.key} className="nre-metric-col">
|
|
36
|
-
{col.label}
|
|
39
|
+
{resolveMetricLabel(col.label, locale, col.key)}
|
|
37
40
|
{col.unit && <span className="nre-unit">({col.unit})</span>}
|
|
38
41
|
</th>
|
|
39
42
|
))}
|
|
@@ -57,16 +60,16 @@ export function DeltaTable({
|
|
|
57
60
|
{/* A/B 走统一的 MetricCellView:缺数据文案与覆盖率角标同一套 */}
|
|
58
61
|
<span className="nre-delta-side nre-delta-a">
|
|
59
62
|
<span className="nre-delta-tag">A</span>
|
|
60
|
-
<MetricCellView cell={cell.a} />
|
|
63
|
+
<MetricCellView cell={cell.a} locale={locale} />
|
|
61
64
|
</span>
|
|
62
65
|
<span className="nre-delta-side nre-delta-b">
|
|
63
66
|
<span className="nre-delta-tag">B</span>
|
|
64
|
-
<MetricCellView cell={cell.b} />
|
|
67
|
+
<MetricCellView cell={cell.b} locale={locale} />
|
|
65
68
|
</span>
|
|
66
69
|
<span className={cx("nre-delta-side", "nre-delta-d", deltaToneClass(cell.delta, col.better))}>
|
|
67
70
|
<span className="nre-delta-tag">Δ</span>
|
|
68
71
|
{/* 任一侧 null → delta null:显示缺,不硬算 */}
|
|
69
|
-
{cell.delta === null ? <span className="nre-missing">{
|
|
72
|
+
{cell.delta === null ? <span className="nre-missing">{localeText(locale, "cell.missing")}</span> : cell.display}
|
|
70
73
|
</span>
|
|
71
74
|
</td>
|
|
72
75
|
);
|
|
Binary file
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// ExperimentList:实体列表的第一级——每项固定代表一个 experiment,主行是身份(experiment id /
|
|
2
|
+
// agent / model / flags)、Eval 判定构成与官方两级聚合汇总指标(passRate/cost/duration/tokens,
|
|
3
|
+
// 复用 MetricCellView 与其它指标图形同一份渲染)。零 JS 用原生 <details> 展开到这个 experiment
|
|
4
|
+
// 的 Eval:每道题一行,判定符 + 该题全部 Attempt 的 locator 徽标(内联,不用再点开一层)+
|
|
5
|
+
// 通过时的平均耗时/成本、失败/出错时的原因摘要。
|
|
6
|
+
|
|
7
|
+
import type { ReactElement } from "react";
|
|
8
|
+
import type { ExperimentListEvalRow, ExperimentListItem } from "../types.ts";
|
|
9
|
+
import type { AttemptLocator } from "../../results/locator.ts";
|
|
10
|
+
import { DEFAULT_REPORT_LOCALE, localeText, type ReportLocale } from "../locale.ts";
|
|
11
|
+
import { MetricCellView } from "./cell.tsx";
|
|
12
|
+
import { AttemptLocatorBadge } from "./AttemptList.tsx";
|
|
13
|
+
import { colorClassForKey } from "./colors.ts";
|
|
14
|
+
import { cx, formatDurationMs, formatUSD, verdictMark } from "./format.ts";
|
|
15
|
+
|
|
16
|
+
function EvalRow({
|
|
17
|
+
row,
|
|
18
|
+
attemptHref,
|
|
19
|
+
}: {
|
|
20
|
+
row: ExperimentListEvalRow;
|
|
21
|
+
attemptHref: (locator: AttemptLocator) => string;
|
|
22
|
+
}): ReactElement {
|
|
23
|
+
return (
|
|
24
|
+
<li className={cx("nre-experiment-eval-row", `nre-eval-${row.verdict}`)}>
|
|
25
|
+
<span className={cx("nre-eval-verdict", `nre-verdict-${row.verdict}`)}>{verdictMark(row.verdict)}</span>
|
|
26
|
+
<span className="nre-eval-id">{row.evalId}</span>
|
|
27
|
+
<span className="nre-eval-attempt-badges">
|
|
28
|
+
{row.attempts.map((attempt) => (
|
|
29
|
+
<AttemptLocatorBadge key={attempt.locator} item={attempt} attemptHref={attemptHref} />
|
|
30
|
+
))}
|
|
31
|
+
</span>
|
|
32
|
+
{row.verdict === "passed" ? (
|
|
33
|
+
<span className="nre-eval-avg">
|
|
34
|
+
{formatDurationMs(row.duration.value ?? 0)}
|
|
35
|
+
{row.cost.value !== null && <> · {formatUSD(row.cost.value)}</>}
|
|
36
|
+
</span>
|
|
37
|
+
) : (
|
|
38
|
+
<span className="nre-eval-reason">{row.reason}</span>
|
|
39
|
+
)}
|
|
40
|
+
</li>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function ExperimentCard({
|
|
45
|
+
item,
|
|
46
|
+
attemptHref,
|
|
47
|
+
locale,
|
|
48
|
+
}: {
|
|
49
|
+
item: ExperimentListItem;
|
|
50
|
+
attemptHref: (locator: AttemptLocator) => string;
|
|
51
|
+
locale: ReportLocale;
|
|
52
|
+
}): ReactElement {
|
|
53
|
+
return (
|
|
54
|
+
<li className="nre-experiment-entry">
|
|
55
|
+
<details className="nre-experiment-details">
|
|
56
|
+
<summary className="nre-experiment-summary">
|
|
57
|
+
<span className={cx("nre-experiment-id", "nre-key", colorClassForKey(item.experimentId))}>
|
|
58
|
+
{item.experimentId}
|
|
59
|
+
</span>
|
|
60
|
+
<span className="nre-experiment-agent">{item.agent}</span>
|
|
61
|
+
{item.model && <span className="nre-experiment-model">{item.model}</span>}
|
|
62
|
+
<span className="nre-experiment-cell">
|
|
63
|
+
<MetricCellView cell={item.passRate} attemptHref={attemptHref} locale={locale} />
|
|
64
|
+
</span>
|
|
65
|
+
<span className="nre-experiment-cell">
|
|
66
|
+
<MetricCellView cell={item.cost} attemptHref={attemptHref} locale={locale} />
|
|
67
|
+
</span>
|
|
68
|
+
<span className="nre-experiment-cell">
|
|
69
|
+
<MetricCellView cell={item.duration} attemptHref={attemptHref} locale={locale} />
|
|
70
|
+
</span>
|
|
71
|
+
<span className="nre-experiment-meta-sub">
|
|
72
|
+
{localeText(locale, "overview.evalsCount", { n: item.evals })}
|
|
73
|
+
{item.attempts > item.evals ? ` · ${localeText(locale, "overview.attemptsCount", { n: item.attempts })}` : ""}
|
|
74
|
+
{` · ${localeText(locale, "latestRun", { run: item.lastRunAt })}`}
|
|
75
|
+
</span>
|
|
76
|
+
</summary>
|
|
77
|
+
<ul className="nre-experiment-evals">
|
|
78
|
+
{item.evalRows.map((row) => (
|
|
79
|
+
<EvalRow key={row.evalId} row={row} attemptHref={attemptHref} />
|
|
80
|
+
))}
|
|
81
|
+
</ul>
|
|
82
|
+
</details>
|
|
83
|
+
</li>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function ExperimentList({
|
|
88
|
+
items,
|
|
89
|
+
attemptHref,
|
|
90
|
+
className,
|
|
91
|
+
locale = DEFAULT_REPORT_LOCALE,
|
|
92
|
+
}: {
|
|
93
|
+
items: ExperimentListItem[];
|
|
94
|
+
attemptHref: (locator: AttemptLocator) => string;
|
|
95
|
+
className?: string;
|
|
96
|
+
locale?: ReportLocale;
|
|
97
|
+
}): ReactElement {
|
|
98
|
+
return (
|
|
99
|
+
<div className={cx("nre", "nre-experiment-list", className)}>
|
|
100
|
+
{items.length === 0 && <p className="nre-experiment-list-empty">{localeText(locale, "attemptList.empty")}</p>}
|
|
101
|
+
<ul className="nre-experiments">
|
|
102
|
+
{items.map((item) => (
|
|
103
|
+
<ExperimentCard key={item.experimentId} item={item} attemptHref={attemptHref} locale={locale} />
|
|
104
|
+
))}
|
|
105
|
+
</ul>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// GroupSummary:一组 experiment(如自定义报告里同一 <Section> 内的全部 experiment)的
|
|
2
|
+
// 紧凑摘要块——恢复旧 GroupSelector 卡片的信息密度(通过率、experiment/eval 数、failed、
|
|
3
|
+
// errored、总成本、最后运行时间),但只渲染 GroupSummaryData 算好的 MetricCell / 计数,
|
|
4
|
+
// 不现场重算比例(docs/feature/reports/library.md「GroupSummary」)。errored 为 0 时省略该片段(旧卡片同一姿态),
|
|
5
|
+
// 但这只是渲染取舍——数据侧 verdicts.errored 字段本身永不省略。
|
|
6
|
+
|
|
7
|
+
import type { ReactElement } from "react";
|
|
8
|
+
import type { GroupSummaryData } from "../types.ts";
|
|
9
|
+
import { DEFAULT_REPORT_LOCALE, localeText, type ReportLocale } from "../locale.ts";
|
|
10
|
+
import { MetricCellView } from "./cell.tsx";
|
|
11
|
+
import { cx, formatUSD } from "./format.ts";
|
|
12
|
+
|
|
13
|
+
export function GroupSummary({
|
|
14
|
+
data,
|
|
15
|
+
className,
|
|
16
|
+
locale = DEFAULT_REPORT_LOCALE,
|
|
17
|
+
}: {
|
|
18
|
+
data: GroupSummaryData;
|
|
19
|
+
className?: string;
|
|
20
|
+
locale?: ReportLocale;
|
|
21
|
+
}): ReactElement {
|
|
22
|
+
const missing = <span className="nre-missing">{localeText(locale, "cell.missing")}</span>;
|
|
23
|
+
return (
|
|
24
|
+
<div className={cx("nre", "nre-group-summary", className)}>
|
|
25
|
+
<dl className="nre-group-kpis">
|
|
26
|
+
<div className="nre-group-kpi nre-group-kpi-rate">
|
|
27
|
+
<dt>{localeText(locale, "overview.passRate")}</dt>
|
|
28
|
+
<dd>
|
|
29
|
+
<MetricCellView cell={data.passRate} locale={locale} />
|
|
30
|
+
</dd>
|
|
31
|
+
</div>
|
|
32
|
+
<div className="nre-group-kpi">
|
|
33
|
+
<dt>{localeText(locale, "groupSummary.experiments")}</dt>
|
|
34
|
+
<dd>{data.experiments}</dd>
|
|
35
|
+
</div>
|
|
36
|
+
<div className="nre-group-kpi">
|
|
37
|
+
<dt>{localeText(locale, "overview.evals")}</dt>
|
|
38
|
+
<dd>{data.evals}</dd>
|
|
39
|
+
</div>
|
|
40
|
+
<div className="nre-group-kpi">
|
|
41
|
+
<dt>{localeText(locale, "overview.attempts")}</dt>
|
|
42
|
+
<dd>{data.attempts}</dd>
|
|
43
|
+
</div>
|
|
44
|
+
<div className="nre-group-kpi">
|
|
45
|
+
<dt>{localeText(locale, "verdict.failed")}</dt>
|
|
46
|
+
<dd className="nre-verdict-failed">{data.verdicts.failed}</dd>
|
|
47
|
+
</div>
|
|
48
|
+
{/* errored 为 0 省略这一片段(旧 GroupSelector 卡片同一姿态);数据字段本身不受影响 */}
|
|
49
|
+
{data.verdicts.errored > 0 && (
|
|
50
|
+
<div className="nre-group-kpi">
|
|
51
|
+
<dt>{localeText(locale, "verdict.errored")}</dt>
|
|
52
|
+
<dd className="nre-verdict-errored">{data.verdicts.errored}</dd>
|
|
53
|
+
</div>
|
|
54
|
+
)}
|
|
55
|
+
<div className="nre-group-kpi">
|
|
56
|
+
<dt>{localeText(locale, "overview.totalCost")}</dt>
|
|
57
|
+
{/* totalCostUSD 全缺 = null:显示缺数据,不编 $0 */}
|
|
58
|
+
<dd>{data.totalCostUSD === null ? missing : formatUSD(data.totalCostUSD)}</dd>
|
|
59
|
+
</div>
|
|
60
|
+
</dl>
|
|
61
|
+
{data.lastRunAt && (
|
|
62
|
+
<p className="nre-group-summary-time">{localeText(locale, "latestRun", { run: data.lastRunAt })}</p>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// MetricBars:分组条形——同一份矩阵数据的另一种摆法(MetricBars.data = MetricMatrix.data)。
|
|
2
2
|
// 组维度一组条、系列维度一根条、条长是指标值;竖向分组柱,柱顶标数值,系列颜色与
|
|
3
|
-
//
|
|
4
|
-
//
|
|
3
|
+
// 其它组件的稳定配色一致(类名 nre-series-cN 由 CSS 上色,深色主题跟随),图例自动生成。
|
|
4
|
+
// 组内按值排序,方向随 better;缺数据的系列不画柱、不编 0(与 text 面的 — 同口径)。
|
|
5
5
|
|
|
6
6
|
import type { ReactElement } from "react";
|
|
7
|
-
import type {
|
|
8
|
-
import {
|
|
7
|
+
import type { MatrixData, MetricCell } from "../types.ts";
|
|
8
|
+
import type { AttemptLocator } from "../../results/locator.ts";
|
|
9
|
+
import { DEFAULT_REPORT_LOCALE, resolveMetricLabel, type ReportLocale } from "../locale.ts";
|
|
10
|
+
import { colorClassForKey, seriesClassForKey } from "./colors.ts";
|
|
9
11
|
import { cx } from "./format.ts";
|
|
10
12
|
|
|
11
13
|
const WIDTH = 640;
|
|
@@ -16,10 +18,12 @@ export function MetricBars({
|
|
|
16
18
|
data,
|
|
17
19
|
attemptHref,
|
|
18
20
|
className,
|
|
21
|
+
locale = DEFAULT_REPORT_LOCALE,
|
|
19
22
|
}: {
|
|
20
23
|
data: MatrixData;
|
|
21
|
-
attemptHref?: (
|
|
24
|
+
attemptHref?: (locator: AttemptLocator) => string;
|
|
22
25
|
className?: string;
|
|
26
|
+
locale?: ReportLocale;
|
|
23
27
|
}): ReactElement {
|
|
24
28
|
// 稀疏 cells → 首次出现顺序的组/系列键 + 查找表
|
|
25
29
|
const groupKeys: string[] = [];
|
|
@@ -31,6 +35,7 @@ export function MetricBars({
|
|
|
31
35
|
byPosition.set(JSON.stringify([entry.row, entry.column]), entry.cell);
|
|
32
36
|
}
|
|
33
37
|
|
|
38
|
+
const metricLabel = resolveMetricLabel(data.metric.label, locale, data.metric.key);
|
|
34
39
|
const better = data.metric.better ?? "higher";
|
|
35
40
|
const values = data.cells.map((c) => c.cell.value).filter((v): v is number => v !== null);
|
|
36
41
|
const maxValue = values.length > 0 ? Math.max(...values) : 0;
|
|
@@ -47,7 +52,7 @@ export function MetricBars({
|
|
|
47
52
|
className="nre-bars-svg"
|
|
48
53
|
viewBox={`0 0 ${WIDTH} ${HEIGHT}`}
|
|
49
54
|
role="img"
|
|
50
|
-
aria-label={`${
|
|
55
|
+
aria-label={`${metricLabel} by ${data.rows} × ${data.columns}`}
|
|
51
56
|
>
|
|
52
57
|
{groupKeys.map((group, gi) => {
|
|
53
58
|
const x0 = PLOT.left + gi * groupWidth;
|
|
@@ -68,34 +73,40 @@ export function MetricBars({
|
|
|
68
73
|
const y = PLOT.bottom - h;
|
|
69
74
|
const title = `${group} · ${series}: ${cell.display}(${cell.samples}/${cell.total})`;
|
|
70
75
|
const rect = (
|
|
71
|
-
<rect
|
|
76
|
+
<rect
|
|
77
|
+
className={cx("nre-bar", seriesClassForKey(series))}
|
|
78
|
+
x={x + 2}
|
|
79
|
+
y={y}
|
|
80
|
+
width={barWidth - 4}
|
|
81
|
+
height={h}
|
|
82
|
+
>
|
|
72
83
|
<title>{title}</title>
|
|
73
84
|
</rect>
|
|
74
85
|
);
|
|
75
|
-
const
|
|
86
|
+
const locator = cell.refs[0];
|
|
76
87
|
return (
|
|
77
88
|
<g key={series}>
|
|
78
|
-
{attemptHref &&
|
|
89
|
+
{attemptHref && locator ? <a href={attemptHref(locator)}>{rect}</a> : rect}
|
|
79
90
|
{/* 柱顶标数值;覆盖不全时把 samples/total 一并标出,不藏 */}
|
|
80
|
-
<text className="nre-bar-value" x={x + barWidth / 2} y={y - 4} textAnchor="middle"
|
|
91
|
+
<text className="nre-bar-value" x={x + barWidth / 2} y={y - 4} textAnchor="middle">
|
|
81
92
|
{cell.samples < cell.total ? `${cell.display} ${cell.samples}/${cell.total}` : cell.display}
|
|
82
93
|
</text>
|
|
83
94
|
</g>
|
|
84
95
|
);
|
|
85
96
|
})}
|
|
86
|
-
<text className="nre-bars-group-label" x={x0 + groupWidth / 2} y={HEIGHT - 20} textAnchor="middle"
|
|
97
|
+
<text className="nre-bars-group-label" x={x0 + groupWidth / 2} y={HEIGHT - 20} textAnchor="middle">
|
|
87
98
|
{group}
|
|
88
99
|
</text>
|
|
89
100
|
</g>
|
|
90
101
|
);
|
|
91
102
|
})}
|
|
92
103
|
{/* 基线 */}
|
|
93
|
-
<line x1={PLOT.left} y1={PLOT.bottom} x2={PLOT.right} y2={PLOT.bottom}
|
|
104
|
+
<line className="nre-bars-baseline" x1={PLOT.left} y1={PLOT.bottom} x2={PLOT.right} y2={PLOT.bottom} />
|
|
94
105
|
</svg>
|
|
95
106
|
{/* 图例:系列 → 稳定配色,跨块同键同色 */}
|
|
96
107
|
<figcaption className="nre-bars-legend">
|
|
97
108
|
<span className="nre-bars-metric">
|
|
98
|
-
{
|
|
109
|
+
{metricLabel}
|
|
99
110
|
{data.metric.unit && <span className="nre-unit">({data.metric.unit})</span>}
|
|
100
111
|
</span>
|
|
101
112
|
{seriesKeys.map((series) => (
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
// MetricLine:趋势线——x 是 experiment 声明的 flag(有序变量),每个系列一条线。
|
|
2
2
|
// 与 MetricScatter 的分工:scatter 的两轴都是测出来的指标(找 frontier),
|
|
3
3
|
// line 的 x 是你配置的变量(看趋势)。x 轴正常升序;y 轴向随 better;
|
|
4
|
-
// x 或 y 缺数据的点不画,注脚如实报数;每个点带 <title> hover
|
|
4
|
+
// x 或 y 缺数据的点不画,注脚如实报数;每个点带 <title> hover(enhance.js 在场时
|
|
5
|
+
// 升级为样式化 tooltip),可经 pointHref 下钻。配色走类名(nre-series-cN)由 CSS
|
|
6
|
+
// 上色,深色主题跟随,不留内联 hex。
|
|
5
7
|
|
|
6
8
|
import type { ReactElement } from "react";
|
|
7
9
|
import type { LineData } from "../types.ts";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
+
import { DEFAULT_REPORT_LOCALE, countText, localeText, resolveMetricLabel, type ReportLocale } from "../locale.ts";
|
|
11
|
+
import { seriesClassForKey } from "./colors.ts";
|
|
12
|
+
import { cx } from "./format.ts";
|
|
10
13
|
|
|
11
14
|
const WIDTH = 640;
|
|
12
15
|
const HEIGHT = 360;
|
|
@@ -39,25 +42,28 @@ export function MetricLine({
|
|
|
39
42
|
data,
|
|
40
43
|
pointHref,
|
|
41
44
|
className,
|
|
45
|
+
locale = DEFAULT_REPORT_LOCALE,
|
|
42
46
|
}: {
|
|
43
47
|
data: LineData;
|
|
44
48
|
pointHref?: (row: LineData["rows"][number]) => string;
|
|
45
49
|
className?: string;
|
|
50
|
+
locale?: ReportLocale;
|
|
46
51
|
}): ReactElement {
|
|
47
52
|
const missing = data.rows.filter((r) => r.x === null || r.y.value === null);
|
|
48
53
|
const drawableRows = data.rows.filter((r) => r.x !== null && r.y.value !== null);
|
|
54
|
+
const yLabel = resolveMetricLabel(data.y.label, locale, data.y.key);
|
|
49
55
|
|
|
50
56
|
const missingNote =
|
|
51
57
|
missing.length > 0 ? (
|
|
52
58
|
<p className="nre-line-missing" title={missing.map((r) => r.key).join(", ")}>
|
|
53
|
-
{
|
|
59
|
+
{countText(locale, "pointsMissing", missing.length)}
|
|
54
60
|
</p>
|
|
55
61
|
) : null;
|
|
56
62
|
|
|
57
63
|
if (drawableRows.length === 0) {
|
|
58
64
|
return (
|
|
59
65
|
<figure className={cx("nre", "nre-metric-line", className)}>
|
|
60
|
-
<p className="nre-missing">{
|
|
66
|
+
<p className="nre-missing">{localeText(locale, "cell.missing")}</p>
|
|
61
67
|
{missingNote}
|
|
62
68
|
</figure>
|
|
63
69
|
);
|
|
@@ -77,7 +83,7 @@ export function MetricLine({
|
|
|
77
83
|
series: r.series,
|
|
78
84
|
xValue: r.x as number,
|
|
79
85
|
yValue: r.y.value as number,
|
|
80
|
-
title: `${r.key}\n${data.x.label}: ${r.xDisplay}\n${
|
|
86
|
+
title: `${r.key}\n${data.x.label}: ${r.xDisplay}\n${yLabel}: ${r.y.display}(${r.y.samples}/${r.y.total})`,
|
|
81
87
|
px: xScale.scale(r.x as number),
|
|
82
88
|
py: yScale.scale(r.y.value as number),
|
|
83
89
|
}));
|
|
@@ -108,7 +114,7 @@ export function MetricLine({
|
|
|
108
114
|
className="nre-line-svg"
|
|
109
115
|
viewBox={`0 0 ${WIDTH} ${HEIGHT}`}
|
|
110
116
|
role="img"
|
|
111
|
-
aria-label={`${
|
|
117
|
+
aria-label={`${yLabel} by ${data.x.label}`}
|
|
112
118
|
>
|
|
113
119
|
<rect
|
|
114
120
|
className="nre-line-plot"
|
|
@@ -117,9 +123,8 @@ export function MetricLine({
|
|
|
117
123
|
width={PLOT.right - PLOT.left}
|
|
118
124
|
height={PLOT.bottom - PLOT.top}
|
|
119
125
|
fill="none"
|
|
120
|
-
stroke="#d4d4d4"
|
|
121
126
|
/>
|
|
122
|
-
<text className="nre-line-xlabel" x={(PLOT.left + PLOT.right) / 2} y={HEIGHT - 8} textAnchor="middle"
|
|
127
|
+
<text className="nre-line-xlabel" x={(PLOT.left + PLOT.right) / 2} y={HEIGHT - 8} textAnchor="middle">
|
|
123
128
|
{data.x.label}
|
|
124
129
|
{data.x.unit ? `(${data.x.unit})` : ""}
|
|
125
130
|
</text>
|
|
@@ -128,42 +133,38 @@ export function MetricLine({
|
|
|
128
133
|
x={16}
|
|
129
134
|
y={(PLOT.top + PLOT.bottom) / 2}
|
|
130
135
|
textAnchor="middle"
|
|
131
|
-
fontSize={12}
|
|
132
|
-
fill="#525252"
|
|
133
136
|
transform={`rotate(-90 16 ${(PLOT.top + PLOT.bottom) / 2})`}
|
|
134
137
|
>
|
|
135
|
-
{
|
|
138
|
+
{yLabel}
|
|
136
139
|
{data.y.unit ? `(${data.y.unit})` : ""}
|
|
137
140
|
</text>
|
|
138
141
|
|
|
139
142
|
{xTicks.map((v) => (
|
|
140
|
-
<text key={`x${v}`} className="nre-line-tick" x={xScale.scale(v)} y={PLOT.bottom + 16} textAnchor="middle"
|
|
143
|
+
<text key={`x${v}`} className="nre-line-tick" x={xScale.scale(v)} y={PLOT.bottom + 16} textAnchor="middle">
|
|
141
144
|
{xDisplayFor(v)}
|
|
142
145
|
</text>
|
|
143
146
|
))}
|
|
144
147
|
{yTicks.map((v) => (
|
|
145
|
-
<text key={`y${v}`} className="nre-line-tick" x={PLOT.left - 6} y={yScale.scale(v) + 4} textAnchor="end"
|
|
148
|
+
<text key={`y${v}`} className="nre-line-tick" x={PLOT.left - 6} y={yScale.scale(v) + 4} textAnchor="end">
|
|
146
149
|
{yDisplayFor(v)}
|
|
147
150
|
</text>
|
|
148
151
|
))}
|
|
149
152
|
|
|
150
153
|
{seriesOrder.map((series) => {
|
|
151
154
|
const list = bySeries.get(series)!;
|
|
152
|
-
const
|
|
155
|
+
const seriesClass = series === "" ? "nre-series-none" : seriesClassForKey(series);
|
|
153
156
|
const labelAt = list.reduce((a, b) => (b.px > a.px ? b : a));
|
|
154
157
|
return (
|
|
155
|
-
<g key={series || "(single)"} className="nre-line-series" data-series={series || undefined}>
|
|
158
|
+
<g key={series || "(single)"} className={cx("nre-line-series", seriesClass)} data-series={series || undefined}>
|
|
156
159
|
{list.length > 1 && (
|
|
157
160
|
<polyline
|
|
158
161
|
className="nre-line-path"
|
|
159
162
|
points={list.map((p) => `${p.px},${p.py}`).join(" ")}
|
|
160
163
|
fill="none"
|
|
161
|
-
stroke={color}
|
|
162
|
-
strokeWidth={1.5}
|
|
163
164
|
/>
|
|
164
165
|
)}
|
|
165
166
|
{series !== "" && (
|
|
166
|
-
<text className="nre-line-series-label" x={labelAt.px + 8} y={labelAt.py + 4}
|
|
167
|
+
<text className="nre-line-series-label" x={labelAt.px + 8} y={labelAt.py + 4}>
|
|
167
168
|
{series}
|
|
168
169
|
</text>
|
|
169
170
|
)}
|
|
@@ -174,12 +175,11 @@ export function MetricLine({
|
|
|
174
175
|
{points.map((p, i) => {
|
|
175
176
|
const circle = (
|
|
176
177
|
<circle
|
|
177
|
-
className="nre-line-point"
|
|
178
|
+
className={cx("nre-line-point", p.series !== undefined ? seriesClassForKey(p.series) : "nre-series-none")}
|
|
178
179
|
data-key={p.key}
|
|
179
180
|
cx={p.px}
|
|
180
181
|
cy={p.py}
|
|
181
182
|
r={4}
|
|
182
|
-
fill={p.series !== undefined ? colorHexForKey(p.series) : "#525252"}
|
|
183
183
|
>
|
|
184
184
|
<title>{p.title}</title>
|
|
185
185
|
</circle>
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
// 是格子里的普通 <a>,不 hydrate 也能点。
|
|
6
6
|
|
|
7
7
|
import type { ReactElement } from "react";
|
|
8
|
-
import type {
|
|
8
|
+
import type { MatrixData } from "../types.ts";
|
|
9
|
+
import type { AttemptLocator } from "../../results/locator.ts";
|
|
10
|
+
import { DEFAULT_REPORT_LOCALE, resolveMetricLabel, type ReportLocale } from "../locale.ts";
|
|
9
11
|
import { MetricCellView } from "./cell.tsx";
|
|
10
12
|
import { colorClassForKey } from "./colors.ts";
|
|
11
13
|
import { cx } from "./format.ts";
|
|
@@ -14,10 +16,12 @@ export function MetricMatrix({
|
|
|
14
16
|
data,
|
|
15
17
|
attemptHref,
|
|
16
18
|
className,
|
|
19
|
+
locale = DEFAULT_REPORT_LOCALE,
|
|
17
20
|
}: {
|
|
18
21
|
data: MatrixData;
|
|
19
|
-
attemptHref?: (
|
|
22
|
+
attemptHref?: (locator: AttemptLocator) => string;
|
|
20
23
|
className?: string;
|
|
24
|
+
locale?: ReportLocale;
|
|
21
25
|
}): ReactElement {
|
|
22
26
|
// 稀疏 cells → 首次出现顺序的行/列键 + 查找表;组件只整理形状,不碰数值
|
|
23
27
|
const rowKeys: string[] = [];
|
|
@@ -33,7 +37,7 @@ export function MetricMatrix({
|
|
|
33
37
|
return (
|
|
34
38
|
<table className={cx("nre", "nre-metric-matrix", className)}>
|
|
35
39
|
<caption className="nre-matrix-caption">
|
|
36
|
-
{data.metric.label}
|
|
40
|
+
{resolveMetricLabel(data.metric.label, locale, data.metric.key)}
|
|
37
41
|
{data.metric.unit && <span className="nre-unit">({data.metric.unit})</span>}
|
|
38
42
|
<span className="nre-matrix-axes">
|
|
39
43
|
{data.rows} × {data.columns}
|
|
@@ -63,7 +67,7 @@ export function MetricMatrix({
|
|
|
63
67
|
return (
|
|
64
68
|
<td key={column} className={cx("nre-td", !cell && "nre-td-empty")}>
|
|
65
69
|
{/* 稀疏格子:没有样本就空着(数据里不存在),不是 0 也不是缺数据文案 */}
|
|
66
|
-
{cell ? <MetricCellView cell={cell} attemptHref={attemptHref} /> : null}
|
|
70
|
+
{cell ? <MetricCellView cell={cell} attemptHref={attemptHref} locale={locale} /> : null}
|
|
67
71
|
</td>
|
|
68
72
|
);
|
|
69
73
|
})}
|