niceeval 0.6.1 → 0.7.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 +23 -23
- package/README.zh.md +6 -6
- package/dist/agents/types.d.ts +69 -7
- package/dist/context/types.d.ts +32 -12
- package/dist/i18n/en.d.ts +54 -0
- package/dist/i18n/zh-CN.d.ts +57 -3
- package/dist/o11y/types.d.ts +16 -2
- package/dist/report/aggregate.d.ts +32 -24
- package/dist/report/aggregate.js +158 -50
- 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 +93 -160
- package/dist/report/components.js +377 -114
- package/dist/report/compute.d.ts +87 -81
- package/dist/report/compute.js +597 -417
- package/dist/report/flag.d.ts +32 -6
- package/dist/report/flag.js +92 -4
- package/dist/report/format.d.ts +19 -11
- package/dist/report/format.js +30 -13
- package/dist/report/index.d.ts +16 -16
- package/dist/report/index.js +20 -21
- package/dist/report/load.js +3 -2
- package/dist/report/locale.d.ts +57 -33
- package/dist/report/locale.js +122 -56
- package/dist/report/metrics.d.ts +23 -4
- package/dist/report/metrics.js +110 -25
- package/dist/report/primitives.d.ts +48 -15
- package/dist/report/primitives.js +135 -26
- package/dist/report/react/AttemptList.d.ts +9 -7
- package/dist/report/react/AttemptList.js +17 -10
- package/dist/report/react/DeltaTable.js +19 -18
- package/dist/report/react/EvalList.d.ts +4 -4
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentComparison.d.ts +10 -0
- package/dist/report/react/ExperimentComparison.js +12 -0
- package/dist/report/react/ExperimentList.d.ts +4 -3
- package/dist/report/react/ExperimentList.js +17 -18
- 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 +59 -28
- 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/chart-math.d.ts +23 -6
- package/dist/report/react/chart-math.js +71 -19
- package/dist/report/react/fixtures.d.ts +5 -9
- package/dist/report/react/fixtures.js +110 -147
- package/dist/report/react/index.d.ts +15 -5
- package/dist/report/react/index.js +18 -7
- package/dist/report/report.d.ts +137 -16
- package/dist/report/report.js +259 -28
- package/dist/report/text/faces.d.ts +17 -19
- package/dist/report/text/faces.js +253 -184
- package/dist/report/text/plot.js +1 -1
- package/dist/report/text/table.js +38 -7
- package/dist/report/tree.d.ts +90 -40
- package/dist/report/tree.js +252 -94
- package/dist/report/types.d.ts +247 -284
- 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 +49 -14
- package/dist/runner/feedback/sink.d.ts +110 -0
- package/dist/runner/types.d.ts +513 -22
- package/dist/sandbox/docker.d.ts +23 -2
- package/dist/sandbox/e2b.d.ts +15 -1
- package/dist/sandbox/errors.d.ts +30 -3
- package/dist/sandbox/io-retry.d.ts +17 -0
- package/dist/sandbox/registry.d.ts +2 -0
- package/dist/sandbox/resolve.d.ts +18 -5
- package/dist/sandbox/retry.d.ts +11 -1
- package/dist/sandbox/types.d.ts +39 -5
- package/dist/sandbox/vercel.d.ts +7 -1
- package/dist/scoring/coverage.d.ts +30 -0
- package/dist/scoring/display.d.ts +21 -0
- package/dist/scoring/display.js +120 -0
- package/dist/scoring/types.d.ts +103 -20
- package/dist/shared/aggregate.d.ts +4 -2
- package/dist/shared/aggregate.js +8 -7
- package/dist/shared/types.d.ts +28 -0
- package/dist/tty-line.d.ts +0 -4
- package/dist/util.d.ts +23 -0
- 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 +31 -13
- 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 +11 -11
- package/docs-site/zh/{guides → explanation}/runner.mdx +18 -8
- package/docs-site/zh/{concepts → explanation}/tier.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/agent-feedback-loop.mdx +35 -33
- package/docs-site/zh/{guides → how-to}/authoring.mdx +35 -2
- package/docs-site/zh/{guides → how-to}/ci-integration.mdx +23 -12
- package/docs-site/zh/{guides → how-to}/connect-otel.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/connect-your-agent.mdx +47 -21
- package/docs-site/zh/{guides → how-to}/custom-reports.mdx +34 -39
- package/docs-site/zh/{guides → how-to}/dataset-fanout.mdx +25 -3
- package/docs-site/zh/{guides → how-to}/experiments.mdx +12 -5
- package/docs-site/zh/how-to/publish-report.mdx +105 -0
- package/docs-site/zh/{guides → how-to}/reporters.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/sandbox-agent.mdx +56 -7
- package/docs-site/zh/how-to/sandbox-providers.mdx +350 -0
- package/docs-site/zh/{guides → how-to}/scoring-guide.mdx +4 -4
- package/docs-site/zh/{guides → how-to}/viewing-results.mdx +82 -39
- package/docs-site/zh/{guides → how-to}/write-experiment.mdx +6 -4
- package/docs-site/zh/{guides → how-to}/write-send.mdx +30 -14
- package/docs-site/zh/index.mdx +24 -26
- package/docs-site/zh/introduction.mdx +8 -8
- package/docs-site/zh/reference/builtin-agents.mdx +32 -5
- package/docs-site/zh/reference/capabilities.mdx +8 -8
- package/docs-site/zh/reference/cli.mdx +40 -12
- package/docs-site/zh/reference/define-agent.mdx +58 -5
- package/docs-site/zh/reference/define-config.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +42 -9
- package/docs-site/zh/reference/events.mdx +3 -3
- package/docs-site/zh/reference/expect.mdx +26 -1
- package/docs-site/zh/{guides → reference}/official-adapters.mdx +32 -8
- package/docs-site/zh/{guides → reference}/report-components.mdx +45 -33
- package/docs-site/zh/{guides → reference}/results-data.mdx +21 -13
- package/docs-site/zh/troubleshooting/debug-sandbox.mdx +57 -0
- package/docs-site/zh/troubleshooting/debugging.mdx +212 -0
- package/docs-site/zh/{quickstart.mdx → tutorials/quickstart.mdx} +5 -17
- package/package.json +10 -2
- package/src/agents/ai-sdk-otel.test.ts +1 -0
- package/src/agents/ai-sdk.test.ts +3 -0
- package/src/agents/ai-sdk.ts +3 -0
- package/src/agents/bub-install-spec.test.ts +34 -0
- package/src/agents/bub-install-spec.ts +32 -0
- package/src/agents/bub.ts +31 -32
- package/src/agents/claude-code.test.ts +130 -9
- package/src/agents/claude-code.ts +76 -4
- package/src/agents/codex.test.ts +189 -40
- package/src/agents/codex.ts +155 -14
- package/src/agents/coding-cli-versions.test.ts +15 -0
- package/src/agents/coding-cli-versions.ts +3 -0
- package/src/agents/index.ts +13 -2
- package/src/agents/langgraph.test.ts +204 -0
- package/src/agents/langgraph.ts +495 -0
- package/src/agents/marketplace.ts +85 -0
- package/src/agents/native-config.test.ts +179 -0
- package/src/agents/native-config.ts +267 -0
- package/src/agents/openai-compat.test.ts +1 -0
- package/src/agents/openai-compat.ts +1 -1
- package/src/agents/openclaw.test.ts +31 -0
- package/src/agents/openclaw.ts +171 -0
- package/src/agents/plugin-config.test.ts +1 -0
- package/src/agents/sdk-streams.test.ts +79 -0
- package/src/agents/sdk-streams.ts +55 -10
- package/src/agents/skills.test.ts +1 -0
- package/src/agents/streaming.test.ts +3 -9
- package/src/agents/streaming.ts +2 -2
- package/src/agents/types.ts +71 -8
- package/src/agents/ui-message-stream.test.ts +3 -0
- package/src/cli.ts +446 -124
- package/src/context/context.test.ts +51 -12
- package/src/context/context.ts +162 -30
- package/src/context/session.test.ts +2 -1
- package/src/context/session.ts +115 -7
- package/src/context/types.ts +30 -12
- package/src/define.test.ts +13 -8
- package/src/define.ts +25 -4
- package/src/expect/index.ts +53 -23
- package/src/i18n/en.ts +81 -17
- package/src/i18n/zh-CN.ts +80 -17
- package/src/o11y/cost.test.ts +1 -0
- package/src/o11y/execution-tree.test.ts +1 -20
- package/src/o11y/otlp/mappers/claude-code.test.ts +1 -0
- package/src/o11y/otlp/parse.test.ts +1 -0
- package/src/o11y/otlp/turn-otel.test.ts +1 -0
- package/src/o11y/parsers/bub.test.ts +1 -0
- package/src/o11y/parsers/claude-code.test.ts +1 -34
- package/src/o11y/parsers/openclaw.test.ts +154 -0
- package/src/o11y/parsers/openclaw.ts +310 -0
- package/src/o11y/prices.json +746 -311
- package/src/o11y/tool-names.test.ts +1 -0
- package/src/o11y/types.ts +16 -2
- package/src/report/aggregate.ts +178 -61
- package/src/report/built-in/index.tsx +9 -0
- package/src/report/components.tsx +625 -279
- package/src/report/compute.ts +723 -491
- package/src/report/dual-render.test.tsx +741 -1024
- package/src/report/flag.ts +104 -12
- package/src/report/format.ts +32 -12
- package/src/report/index.ts +119 -46
- package/src/report/load.ts +3 -2
- package/src/report/locale.ts +136 -65
- package/src/report/metrics.ts +108 -25
- package/src/report/primitives.tsx +196 -45
- package/src/report/react/AttemptList.tsx +30 -43
- package/src/report/react/DeltaTable.tsx +63 -45
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentComparison.tsx +73 -0
- package/src/report/react/ExperimentList.tsx +50 -32
- 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 +86 -34
- 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/chart-math.test.ts +85 -0
- package/src/report/react/chart-math.ts +101 -22
- package/src/report/react/enhance.js +89 -5
- package/src/report/react/fixtures.ts +114 -154
- package/src/report/react/index.tsx +24 -39
- package/src/report/react/render.test.tsx +138 -158
- package/src/report/react/styles.css +243 -82
- package/src/report/report.test.ts +779 -841
- package/src/report/report.ts +423 -41
- package/src/report/text/faces.ts +290 -193
- package/src/report/text/plot.ts +1 -1
- package/src/report/text/table.ts +44 -7
- package/src/report/tree.ts +362 -104
- package/src/report/types.ts +261 -271
- package/src/report/web.ts +63 -20
- package/src/results/annotated-source.test.ts +62 -9
- package/src/results/annotated-source.ts +64 -6
- package/src/results/attempt-evidence.test.ts +13 -11
- package/src/results/attempt-evidence.ts +20 -13
- package/src/results/attempt-source.ts +6 -3
- package/src/results/copy.ts +150 -60
- package/src/results/host-equivalence.test.ts +34 -20
- package/src/results/index.ts +12 -4
- package/src/results/locator.test.ts +1 -22
- package/src/results/open.ts +15 -5
- package/src/results/publish.ts +149 -0
- package/src/results/results.test.ts +89 -54
- package/src/results/select.ts +104 -34
- package/src/results/truncate.ts +90 -0
- package/src/results/types.ts +43 -14
- package/src/results/writer.ts +31 -13
- package/src/runner/attempt.test.ts +138 -7
- package/src/runner/attempt.ts +603 -104
- package/src/runner/discover.test.ts +47 -0
- package/src/runner/discover.ts +36 -2
- package/src/runner/eval-source.test.ts +1 -27
- package/src/runner/feedback/agent.test.ts +504 -0
- package/src/runner/feedback/agent.ts +409 -0
- package/src/runner/feedback/ci.test.ts +562 -0
- package/src/runner/feedback/ci.ts +401 -0
- package/src/runner/feedback/coordinator.test.ts +317 -0
- package/src/runner/feedback/coordinator.ts +397 -0
- package/src/runner/feedback/failure.ts +40 -0
- package/src/runner/feedback/human.test.ts +616 -0
- package/src/runner/feedback/human.ts +535 -0
- package/src/runner/feedback/index.ts +66 -0
- package/src/runner/feedback/io.ts +78 -0
- package/src/runner/feedback/profile.test.ts +50 -0
- package/src/runner/feedback/profile.ts +58 -0
- package/src/runner/feedback/reducer.test.ts +395 -0
- package/src/runner/feedback/reducer.ts +260 -0
- package/src/runner/feedback/renderer.ts +82 -0
- package/src/runner/feedback/sink.ts +203 -0
- package/src/runner/feedback/testing.ts +106 -0
- package/src/runner/ledger.test.ts +230 -0
- package/src/runner/ledger.ts +329 -0
- package/src/runner/report.test.ts +128 -3
- package/src/runner/report.ts +33 -9
- package/src/runner/reporters/artifacts.ts +8 -2
- package/src/runner/reporters/braintrust.test.ts +8 -7
- package/src/runner/reporters/braintrust.ts +9 -2
- package/src/runner/reporters/index.ts +2 -2
- package/src/runner/reporters/json.test.ts +162 -0
- package/src/runner/reporters/json.ts +35 -8
- package/src/runner/reporters/shared.ts +1 -5
- package/src/runner/run.test.ts +760 -3
- package/src/runner/run.ts +243 -37
- package/src/runner/sandbox-prep.ts +3 -42
- package/src/runner/timing.ts +158 -0
- package/src/runner/types.ts +518 -22
- package/src/sandbox/checkpoint.test.ts +55 -0
- package/src/sandbox/checkpoint.ts +29 -8
- package/src/sandbox/cli-commands.ts +407 -0
- package/src/sandbox/docker.ts +115 -16
- package/src/sandbox/e2b-agent-template.test.ts +56 -0
- package/src/sandbox/e2b-agent-template.ts +94 -0
- package/src/sandbox/e2b.ts +74 -9
- package/src/sandbox/errors.ts +111 -4
- package/src/sandbox/index.ts +2 -0
- package/src/sandbox/io-retry.test.ts +58 -0
- package/src/sandbox/io-retry.ts +45 -0
- package/src/sandbox/keep-registry.test.ts +86 -0
- package/src/sandbox/keep-registry.ts +142 -0
- package/src/sandbox/keep.ts +178 -0
- package/src/sandbox/paths.test.ts +1 -0
- package/src/sandbox/paths.ts +19 -8
- package/src/sandbox/registry.ts +20 -3
- package/src/sandbox/resolve.ts +76 -11
- package/src/sandbox/retry.test.ts +70 -0
- package/src/sandbox/retry.ts +46 -4
- package/src/sandbox/types.ts +44 -6
- package/src/sandbox/vercel.ts +43 -20
- package/src/scoring/collector.ts +60 -17
- package/src/scoring/coverage.ts +95 -0
- package/src/scoring/diff.ts +81 -0
- package/src/scoring/display.test.ts +121 -0
- package/src/scoring/display.ts +133 -0
- package/src/scoring/evidence.test.ts +189 -0
- package/src/scoring/judge.test.ts +142 -0
- package/src/scoring/judge.ts +15 -18
- package/src/scoring/scoped.ts +217 -50
- package/src/scoring/types.ts +117 -20
- package/src/scoring/verdict.ts +16 -4
- package/src/shared/aggregate.ts +8 -6
- package/src/shared/types.ts +31 -0
- package/src/show/compose.ts +50 -67
- package/src/show/index.ts +127 -56
- package/src/show/render.ts +662 -131
- package/src/show/report-host.test.ts +188 -0
- package/src/show/report-host.ts +375 -0
- package/src/show/show.test.ts +320 -54
- package/src/tty-line.ts +8 -26
- package/src/util.test.ts +1 -0
- package/src/util.ts +41 -0
- package/src/view/app/App.test.tsx +69 -0
- package/src/view/app/App.tsx +144 -48
- package/src/view/app/components/AttemptModal.tsx +423 -11
- package/src/view/app/components/CodeView.tsx +41 -14
- package/src/view/app/components/CopyControls.tsx +2 -2
- package/src/view/app/i18n.ts +37 -17
- package/src/view/app/lib/attempt-route.test.ts +1 -0
- package/src/view/app/lib/verdict.ts +7 -9
- 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 +2 -1
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +17 -17
- package/src/view/data.test.ts +10 -3
- package/src/view/data.ts +155 -49
- package/src/view/index.ts +56 -41
- package/src/view/server.ts +37 -15
- package/src/view/shared/types.ts +34 -5
- package/src/view/styles.css +227 -0
- package/src/view/view-report.test.ts +167 -62
- package/dist/report/built-ins/experiment-comparison.d.ts +0 -1
- package/dist/report/built-ins/experiment-comparison.js +0 -13
- package/dist/report/built-ins/index.d.ts +0 -1
- 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/docs-site/zh/guides/publish-report.mdx +0 -91
- package/docs-site/zh/guides/sandbox-providers.mdx +0 -102
- package/src/report/built-in-user-parity.test.tsx +0 -640
- package/src/report/built-ins/experiment-comparison.tsx +0 -19
- package/src/report/built-ins/index.ts +0 -2
- package/src/report/react/GroupSummary.tsx +0 -66
- package/src/report/react/RunOverview.tsx +0 -109
- package/src/runner/reporters/console.ts +0 -70
- package/src/runner/reporters/live.test.ts +0 -56
- package/src/runner/reporters/live.ts +0 -247
- package/src/runner/reporters/quiet.test.ts +0 -66
- package/src/runner/reporters/quiet.ts +0 -49
- package/src/runner/reporters/table.ts +0 -277
- /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}/fixtures.mdx +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// ExperimentComparison 的 web 面:完整持有所有可比组,组选择只切换已经独立计算好的 panel。
|
|
2
|
+
// 静态 HTML 用原生 <details> 保留每组完整内容(第一组默认展开、无 JS 完整可读);
|
|
3
|
+
// enhance.js 再把上方摘要卡变成单选切换。切组是纯浏览状态,不重新计算任何数字。
|
|
4
|
+
|
|
5
|
+
import type { ReactElement } from "react";
|
|
6
|
+
import type { ExperimentComparisonData } from "../types.ts";
|
|
7
|
+
import { DEFAULT_REPORT_LOCALE, localeText, type ReportLocale } from "../locale.ts";
|
|
8
|
+
import { ScopeSummary } from "./ScopeSummary.tsx";
|
|
9
|
+
import { MetricScatter } from "./MetricScatter.tsx";
|
|
10
|
+
import { ExperimentList } from "./ExperimentList.tsx";
|
|
11
|
+
import type { AttemptLocator } from "../../results/locator.ts";
|
|
12
|
+
import { cx } from "./format.ts";
|
|
13
|
+
|
|
14
|
+
export function ExperimentComparisonView({
|
|
15
|
+
data,
|
|
16
|
+
className,
|
|
17
|
+
locale = DEFAULT_REPORT_LOCALE,
|
|
18
|
+
attemptHref,
|
|
19
|
+
}: {
|
|
20
|
+
data: ExperimentComparisonData;
|
|
21
|
+
className?: string;
|
|
22
|
+
locale?: ReportLocale;
|
|
23
|
+
attemptHref?: (locator: AttemptLocator) => string;
|
|
24
|
+
}): ReactElement {
|
|
25
|
+
if (data.groups.length === 0) {
|
|
26
|
+
return (
|
|
27
|
+
<div className={cx("nre", "nre-experiment-comparison", className)}>
|
|
28
|
+
<p className="nre-experiment-groups-empty">{localeText(locale, "experimentComparison.empty")}</p>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className={cx("nre", "nre-experiment-comparison", className)} data-nre-experiment-groups>
|
|
35
|
+
<div
|
|
36
|
+
className="nre-experiment-group-tabs"
|
|
37
|
+
role="tablist"
|
|
38
|
+
aria-label={localeText(locale, "experimentComparison.groups")}
|
|
39
|
+
>
|
|
40
|
+
{data.groups.map((group, index) => (
|
|
41
|
+
<div
|
|
42
|
+
key={group.key}
|
|
43
|
+
className="nre-experiment-group-tab"
|
|
44
|
+
role="tab"
|
|
45
|
+
tabIndex={index === 0 ? 0 : -1}
|
|
46
|
+
aria-selected={index === 0 ? "true" : "false"}
|
|
47
|
+
aria-label={localeText(locale, "experimentComparison.selectGroup", { group: group.key })}
|
|
48
|
+
data-nre-experiment-group-select={index}
|
|
49
|
+
>
|
|
50
|
+
<strong className="nre-experiment-group-name">{group.key}</strong>
|
|
51
|
+
<ScopeSummary data={group.summary} locale={locale} />
|
|
52
|
+
</div>
|
|
53
|
+
))}
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div className="nre-experiment-group-panels">
|
|
57
|
+
{data.groups.map((group, index) => (
|
|
58
|
+
<details
|
|
59
|
+
key={group.key}
|
|
60
|
+
className="nre-experiment-group-panel"
|
|
61
|
+
data-nre-experiment-group-panel={index}
|
|
62
|
+
role="tabpanel"
|
|
63
|
+
open={index === 0}
|
|
64
|
+
>
|
|
65
|
+
<summary>{group.key}</summary>
|
|
66
|
+
<MetricScatter data={group.scatter} locale={locale} />
|
|
67
|
+
<ExperimentList data={group.experiments} filter locale={locale} relativeTo={group.key} attemptHref={attemptHref} />
|
|
68
|
+
</details>
|
|
69
|
+
))}
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -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,18 +79,19 @@ function EvalAttempts({
|
|
|
73
79
|
attemptHref: (locator: AttemptLocator) => string;
|
|
74
80
|
locale: ReportLocale;
|
|
75
81
|
}): ReactElement {
|
|
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);
|
|
76
84
|
return (
|
|
77
85
|
<li className="nre-experiment-eval">
|
|
86
|
+
{/* Eval 父行只显示折叠判定、Attempt 数与题级平均;失败原因只在 Attempt 子行 */}
|
|
78
87
|
<div className={cx("nre-experiment-eval-header", `nre-eval-${row.verdict}`)}>
|
|
79
88
|
<span className={cx("nre-eval-verdict", `nre-verdict-${row.verdict}`)}>{verdictMark(row.verdict)}</span>
|
|
80
89
|
<span className="nre-eval-id">{row.evalId}</span>
|
|
81
90
|
<span className="nre-eval-attempt-count">{localeText(locale, "overview.attemptsCount", { n: row.attempts.length })}</span>
|
|
82
|
-
<span className="nre-eval-
|
|
83
|
-
{
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
.join(" · ")
|
|
87
|
-
: row.reason}
|
|
91
|
+
<span className="nre-eval-rollup">
|
|
92
|
+
{localeText(locale, "entityList.average", { value: duration })}
|
|
93
|
+
{" · "}
|
|
94
|
+
{localeText(locale, "entityList.average", { value: cost })}
|
|
88
95
|
</span>
|
|
89
96
|
</div>
|
|
90
97
|
<ul className="nre-experiment-attempts">
|
|
@@ -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
|
);
|
|
@@ -117,16 +125,21 @@ function ExperimentRow({
|
|
|
117
125
|
item,
|
|
118
126
|
attemptHref,
|
|
119
127
|
locale,
|
|
128
|
+
relativeTo,
|
|
120
129
|
}: {
|
|
121
130
|
item: ExperimentListItem;
|
|
122
131
|
attemptHref: (locator: AttemptLocator) => string;
|
|
123
132
|
locale: ReportLocale;
|
|
133
|
+
relativeTo?: string;
|
|
124
134
|
}): ReactElement {
|
|
125
135
|
return (
|
|
126
136
|
<details className="nre-experiment-entry">
|
|
127
137
|
<summary className="nre-experiment-summary">
|
|
138
|
+
{/* relativeTo 只影响显示末段;完整 id 仍是排序 / 着色 / 过滤 / 折叠的键 */}
|
|
128
139
|
<span className="nre-experiment-name" data-sort-value={item.experimentId}>
|
|
129
|
-
<b className={cx("nre-experiment-id", "nre-key", colorClassForKey(item.experimentId))}>
|
|
140
|
+
<b className={cx("nre-experiment-id", "nre-key", colorClassForKey(item.experimentId))}>
|
|
141
|
+
{experimentDisplayName(item.experimentId, relativeTo)}
|
|
142
|
+
</b>
|
|
130
143
|
<small>
|
|
131
144
|
{localeText(locale, "overview.evalsCount", { n: item.evals })}
|
|
132
145
|
{item.attempts > item.evals ? ` · ${localeText(locale, "overview.attemptsCount", { n: item.attempts })}` : ""}
|
|
@@ -135,19 +148,22 @@ function ExperimentRow({
|
|
|
135
148
|
</span>
|
|
136
149
|
<span data-sort-value={item.model ?? ""}>{item.model ?? localeText(locale, "experimentList.defaultModel")}</span>
|
|
137
150
|
<span data-sort-value={item.agent}>{item.agent}</span>
|
|
138
|
-
<span className="nre-num" data-sort-value={item.
|
|
139
|
-
<MetricCellView cell={item.
|
|
151
|
+
<span className="nre-num" data-sort-value={item.durationMs.value ?? ""}>
|
|
152
|
+
<MetricCellView cell={item.durationMs} locale={locale} />
|
|
140
153
|
</span>
|
|
141
|
-
<span
|
|
142
|
-
|
|
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} />
|
|
143
159
|
</span>
|
|
144
160
|
<span className="nre-num" data-sort-value={item.tokens.value ?? ""}>
|
|
145
161
|
<MetricCellView cell={item.tokens} locale={locale} />
|
|
146
162
|
</span>
|
|
147
|
-
<span className="nre-num" data-sort-value={item.
|
|
148
|
-
<MetricCellView cell={item.
|
|
163
|
+
<span className="nre-num" data-sort-value={item.costUSD.value ?? ""}>
|
|
164
|
+
<MetricCellView cell={item.costUSD} locale={locale} />
|
|
149
165
|
</span>
|
|
150
|
-
<span data-sort-value={item.
|
|
166
|
+
<span data-sort-value={item.evalVerdicts.passed}><VerdictSummary item={item} locale={locale} /></span>
|
|
151
167
|
</summary>
|
|
152
168
|
<div className="nre-experiment-detail">
|
|
153
169
|
<Flags flags={item.flags} locale={locale} />
|
|
@@ -162,17 +178,19 @@ function ExperimentRow({
|
|
|
162
178
|
}
|
|
163
179
|
|
|
164
180
|
export function ExperimentList({
|
|
165
|
-
|
|
166
|
-
attemptHref,
|
|
181
|
+
data,
|
|
182
|
+
attemptHref = DEFAULT_ATTEMPT_HREF,
|
|
167
183
|
filter = false,
|
|
168
184
|
className,
|
|
169
185
|
locale = DEFAULT_REPORT_LOCALE,
|
|
186
|
+
relativeTo,
|
|
170
187
|
}: {
|
|
171
|
-
|
|
172
|
-
attemptHref
|
|
188
|
+
data: readonly ExperimentListItem[];
|
|
189
|
+
attemptHref?: (locator: AttemptLocator) => string;
|
|
173
190
|
filter?: boolean;
|
|
174
191
|
className?: string;
|
|
175
192
|
locale?: ReportLocale;
|
|
193
|
+
relativeTo?: string;
|
|
176
194
|
}): ReactElement {
|
|
177
195
|
const labels = [
|
|
178
196
|
localeText(locale, "experimentList.experiment"),
|
|
@@ -181,7 +199,7 @@ export function ExperimentList({
|
|
|
181
199
|
localeText(locale, "experimentList.avgDuration"),
|
|
182
200
|
localeText(locale, "experimentList.passRate"),
|
|
183
201
|
localeText(locale, "experimentList.tokens"),
|
|
184
|
-
localeText(locale, "experimentList.
|
|
202
|
+
localeText(locale, "experimentList.cost"),
|
|
185
203
|
localeText(locale, "experimentList.result"),
|
|
186
204
|
];
|
|
187
205
|
const board = (
|
|
@@ -198,9 +216,9 @@ export function ExperimentList({
|
|
|
198
216
|
</button>
|
|
199
217
|
))}
|
|
200
218
|
</div>
|
|
201
|
-
{
|
|
202
|
-
{
|
|
203
|
-
<ExperimentRow key={item.experimentId} item={item} attemptHref={attemptHref} locale={locale} />
|
|
219
|
+
{data.length === 0 && <p className="nre-experiment-list-empty">{localeText(locale, "attemptList.empty")}</p>}
|
|
220
|
+
{data.map((item) => (
|
|
221
|
+
<ExperimentRow key={item.experimentId} item={item} attemptHref={attemptHref} locale={locale} relativeTo={relativeTo} />
|
|
204
222
|
))}
|
|
205
223
|
</div>
|
|
206
224
|
);
|
|
@@ -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,6 +1,8 @@
|
|
|
1
1
|
// MetricScatter:质量 × 成本 frontier 的积木,内联 SVG、零图表库、零 hooks。
|
|
2
|
-
// 轴向随 better:"lower"
|
|
3
|
-
//
|
|
2
|
+
// 轴向随 better:"lower" 的轴反向画(值大在左/下),「更好」恒指向右上;方向提示恒为
|
|
3
|
+
// 「越靠右上越好」且仅当两轴都声明 better 时显示;刻度显示真实值。
|
|
4
|
+
// niceTicks 刻度 + 网格线;每个点直接标注(placePointLabels 候选位择优:避开其它标签、
|
|
5
|
+
// 数据点与画布边界,离开左右紧邻位时补 leader line);
|
|
4
6
|
// 同系列的点按 x 值排序连线,系列图例列在图下。x 或 y 为 null 的点不画,
|
|
5
7
|
// 底部注脚如实报「n 个点缺数据」;hover 信息退化为 SVG <title>,不 hydrate 也在
|
|
6
8
|
// (enhance.js 在场时升级为样式化 tooltip)。配色走类名(nre-series-cN)由 CSS 上色,
|
|
@@ -9,8 +11,8 @@
|
|
|
9
11
|
import type { ReactElement } from "react";
|
|
10
12
|
import type { MetricColumn, ScatterData } from "../types.ts";
|
|
11
13
|
import { formatMetricValue } from "../format.ts";
|
|
12
|
-
import { DEFAULT_REPORT_LOCALE, countText, localeText, resolveMetricLabel, type ReportLocale } from "../locale.ts";
|
|
13
|
-
import {
|
|
14
|
+
import { DEFAULT_REPORT_LOCALE, countText, localeText, resolveLocalizedText, resolveMetricLabel, type ReportLocale } from "../locale.ts";
|
|
15
|
+
import { niceTicks, placePointLabels } from "./chart-math.ts";
|
|
14
16
|
import { colorClassForKey, seriesClassForKey } from "./colors.ts";
|
|
15
17
|
import { cx } from "./format.ts";
|
|
16
18
|
|
|
@@ -32,22 +34,54 @@ interface DrawablePoint {
|
|
|
32
34
|
py: number;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
|
|
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
|
+
}
|
|
38
71
|
}
|
|
39
72
|
|
|
40
73
|
/**
|
|
41
|
-
* 一根轴:niceTicks 撑出整齐的值域,值 →
|
|
42
|
-
* better: "lower"
|
|
74
|
+
* 一根轴:niceTicks 撑出整齐的值域,值 → 像素做线性映射。轴方向跟随指标的 `better`:
|
|
75
|
+
* `better: "lower"` 的轴反向渲染(值大的一端在左 / 下),「更好」因此恒指向右与上;
|
|
76
|
+
* 刻度标签始终显示真实值,反向只改方向不改数字(docs/feature/reports/library/metric-views.md)。
|
|
43
77
|
*/
|
|
44
|
-
function axisScale(values: number[],
|
|
78
|
+
function axisScale(values: number[], pixelLo: number, pixelHi: number, invert: boolean) {
|
|
45
79
|
const ticks = niceTicks(Math.min(...values), Math.max(...values), 5);
|
|
46
80
|
const lo = ticks[0];
|
|
47
81
|
const hi = ticks[ticks.length - 1];
|
|
48
82
|
const scale = (v: number) => {
|
|
49
83
|
let t = (v - lo) / (hi - lo || 1);
|
|
50
|
-
if (
|
|
84
|
+
if (invert) t = 1 - t;
|
|
51
85
|
return pixelLo + t * (pixelHi - pixelLo);
|
|
52
86
|
};
|
|
53
87
|
return { ticks, scale };
|
|
@@ -90,9 +124,24 @@ export function MetricScatter({
|
|
|
90
124
|
|
|
91
125
|
const axisLabel = (label: string, col: MetricColumn) => `${label}${col.unit ? `(${col.unit})` : ""}`;
|
|
92
126
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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;
|
|
96
145
|
|
|
97
146
|
const points: DrawablePoint[] = drawableRows.map((r) => {
|
|
98
147
|
const xValue = r.x.value as number;
|
|
@@ -100,12 +149,11 @@ export function MetricScatter({
|
|
|
100
149
|
return {
|
|
101
150
|
key: r.key,
|
|
102
151
|
series: r.series,
|
|
103
|
-
label:
|
|
152
|
+
label: labelByKey.get(r.key) ?? r.key,
|
|
104
153
|
xValue,
|
|
105
154
|
yValue,
|
|
106
|
-
// hover 内容:experiment(点键)+ 系列(
|
|
107
|
-
|
|
108
|
-
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})`,
|
|
109
157
|
px: xScale.scale(xValue),
|
|
110
158
|
py: yScale.scale(yValue),
|
|
111
159
|
};
|
|
@@ -124,12 +172,11 @@ export function MetricScatter({
|
|
|
124
172
|
}
|
|
125
173
|
for (const list of bySeries.values()) list.sort((a, b) => a.xValue - b.xValue);
|
|
126
174
|
|
|
127
|
-
//
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const labelOffsets = layoutLabelOffsets(positions);
|
|
175
|
+
// 直接标签的候选位择优布局:锚向、避让方向都由布局按空间决定,画布边界含边距
|
|
176
|
+
const labels = placePointLabels(
|
|
177
|
+
points.map((p) => ({ cx: p.px, cy: p.py, width: p.label.length * 6.4 + 10 })),
|
|
178
|
+
{ x0: 2, y0: 2, x1: WIDTH - 2, y1: HEIGHT - 2 },
|
|
179
|
+
);
|
|
133
180
|
|
|
134
181
|
return (
|
|
135
182
|
<figure className={cx("nre", "nre-metric-scatter", className)}>
|
|
@@ -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">
|
|
@@ -202,20 +256,18 @@ export function MetricScatter({
|
|
|
202
256
|
{/* 点:g 内带 <title>(无 JS 的原生 hover)、直接标签与 leader line;
|
|
203
257
|
pointHref 时包普通 <a>,静态导出也能下钻 */}
|
|
204
258
|
{points.map((p, i) => {
|
|
205
|
-
const
|
|
206
|
-
const labelX = p.px + (anchorLeft ? -10 : 10);
|
|
207
|
-
const labelY = p.py + 4 + labelOffsets[i];
|
|
259
|
+
const label = labels[i];
|
|
208
260
|
const group = (
|
|
209
261
|
<g
|
|
210
262
|
className={cx("nre-scatter-point", p.series !== undefined ? seriesClassForKey(p.series) : "nre-series-none")}
|
|
211
263
|
data-key={p.key}
|
|
212
264
|
>
|
|
213
265
|
<title>{p.title}</title>
|
|
214
|
-
{/*
|
|
215
|
-
{
|
|
266
|
+
{/* 标签不在紧邻位时补一条 leader line,避免脱离原点看不出对应关系 */}
|
|
267
|
+
{label.leader && <line className="nre-leader" x1={p.px} y1={p.py} x2={label.x} y2={label.y - 4} />}
|
|
216
268
|
<circle className="nre-scatter-hit" cx={p.px} cy={p.py} r={12} />
|
|
217
269
|
<circle className="nre-scatter-dot" cx={p.px} cy={p.py} r={4.5} />
|
|
218
|
-
<text className="nre-scatter-point-label" x={
|
|
270
|
+
<text className="nre-scatter-point-label" x={label.x} y={label.y} textAnchor={label.anchor}>
|
|
219
271
|
{p.label}
|
|
220
272
|
</text>
|
|
221
273
|
</g>
|