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
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
// 排版原语 Row / Col / Section / Text / Style / Table
|
|
2
|
-
// web 面是普通 React 渲染;
|
|
3
|
-
//
|
|
4
|
-
// text
|
|
5
|
-
// Table 是自定义表的标准件,官方表状组件的 text 面也建在它上面(见 ./text/table.ts)。
|
|
1
|
+
// 排版原语 Row / Col / Section / Text / Style / Tabs / Tab / Table:八个内置双面组件,
|
|
2
|
+
// 没有特殊机制(docs/feature/reports/library/layout.md)。web 面是普通 React 渲染;
|
|
3
|
+
// text 面用 ctx.render(child, 子宽) 显式传宽。Style 注入页级全局 CSS(树位置只决定声明
|
|
4
|
+
// 顺序),text 面渲染为空。Table 是自定义表的标准件,官方表状组件的 text 面也建在它上面。
|
|
6
5
|
|
|
7
6
|
import type { ReactNode } from "react";
|
|
8
7
|
import type { AttemptLocator } from "../results/locator.ts";
|
|
9
|
-
import { defineComponent, type ReportNode } from "./tree.ts";
|
|
10
|
-
import { localeText, type ReportLocale } from "./locale.ts";
|
|
11
|
-
import { indentBlock, joinColumns,
|
|
8
|
+
import { COMPONENT_RAW_CHILDREN, COMPONENT_ROLE, defineComponent, type ReportNode } from "./tree.ts";
|
|
9
|
+
import { localeText, resolveLocalizedText, type LocalizedText, type ReportLocale } from "./locale.ts";
|
|
10
|
+
import { indentBlock, joinColumns, stringWidth, wrapDisplay } from "./text/layout.ts";
|
|
11
|
+
import type { ColumnAlign } from "./text/layout.ts";
|
|
12
12
|
import { renderTableText } from "./text/table.ts";
|
|
13
13
|
|
|
14
14
|
function childArray(children: ReportNode): ReportNode[] {
|
|
15
15
|
if (children === null || children === undefined || typeof children === "boolean") return [];
|
|
16
|
-
return Array.isArray(children) ? children : [children];
|
|
16
|
+
return Array.isArray(children) ? [...children] : [children];
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
function cx(...parts: (string | undefined)[]): string {
|
|
@@ -25,8 +25,11 @@ export interface LayoutProps {
|
|
|
25
25
|
className?: string;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
export
|
|
28
|
+
export type RowProps = LayoutProps;
|
|
29
|
+
export type ColProps = LayoutProps;
|
|
30
|
+
|
|
31
|
+
/** 纵向依次排列:网页是块级堆叠,终端是逐块输出(块间空一行)。两面都按声明序。 */
|
|
32
|
+
export const Col = defineComponent<ColProps>({
|
|
30
33
|
web({ children, className }) {
|
|
31
34
|
return <div className={cx("nre", "nre-col", className)}>{children as ReactNode}</div>;
|
|
32
35
|
},
|
|
@@ -39,12 +42,18 @@ export const Col = defineComponent<LayoutProps>({
|
|
|
39
42
|
});
|
|
40
43
|
Col.displayName = "Col";
|
|
41
44
|
|
|
42
|
-
// Row 的每栏至少留这个宽度,不硬挤;不够就降级纵向
|
|
43
|
-
const MIN_COLUMN_WIDTH = 24;
|
|
44
45
|
const COLUMN_SEPARATOR = " │ ";
|
|
45
46
|
|
|
46
|
-
/**
|
|
47
|
-
|
|
47
|
+
/** 一段已渲染文本的自然显示宽度(最长一行)。 */
|
|
48
|
+
function blockWidth(block: string): number {
|
|
49
|
+
return Math.max(...block.split("\n").map((line) => stringWidth(line)), 0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 并排:web 面横排;text 面在可用宽度装得下全部子块时按显示宽度并排(与 `columns` 工具
|
|
54
|
+
* 同一把尺),装不下时整块退化为纵向堆叠——不截断、不隐藏任何子块。
|
|
55
|
+
*/
|
|
56
|
+
export const Row = defineComponent<RowProps>({
|
|
48
57
|
web({ children, className }) {
|
|
49
58
|
return <div className={cx("nre", "nre-row", className)}>{children as ReactNode}</div>;
|
|
50
59
|
},
|
|
@@ -54,62 +63,70 @@ export const Row = defineComponent<LayoutProps>({
|
|
|
54
63
|
);
|
|
55
64
|
if (blocks.length === 0) return "";
|
|
56
65
|
if (blocks.length === 1) return ctx.render(blocks[0]);
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
.filter((block) => block.length > 0)
|
|
65
|
-
.join("\n\n");
|
|
66
|
+
const rendered = blocks.map((child) => ctx.render(child)).filter((block) => block.length > 0);
|
|
67
|
+
if (rendered.length === 0) return "";
|
|
68
|
+
const widths = rendered.map(blockWidth);
|
|
69
|
+
const total = widths.reduce((sum, w) => sum + w, 0) + COLUMN_SEPARATOR.length * (rendered.length - 1);
|
|
70
|
+
if (total > ctx.width) {
|
|
71
|
+
// 装不下:整块退化为纵向堆叠,与 Col 同一形态
|
|
72
|
+
return rendered.join("\n\n");
|
|
66
73
|
}
|
|
67
|
-
|
|
68
|
-
return joinColumns(rendered, rendered.map(() => columnWidth), COLUMN_SEPARATOR);
|
|
74
|
+
return joinColumns(rendered, widths, COLUMN_SEPARATOR);
|
|
69
75
|
},
|
|
70
76
|
});
|
|
71
77
|
Row.displayName = "Row";
|
|
72
78
|
|
|
73
79
|
export interface SectionProps extends LayoutProps {
|
|
74
|
-
title:
|
|
80
|
+
title: LocalizedText;
|
|
75
81
|
}
|
|
76
82
|
|
|
77
83
|
/** 带标题的块:网页是标题层级,终端是标题行加缩进。 */
|
|
78
84
|
export const Section = defineComponent<SectionProps>({
|
|
79
|
-
web({ title, children, className }) {
|
|
85
|
+
web({ title, children, className }, ctx) {
|
|
80
86
|
return (
|
|
81
87
|
<section className={cx("nre", "nre-section", className)}>
|
|
82
|
-
<h2 className="nre-section-title">{title}</h2>
|
|
88
|
+
<h2 className="nre-section-title">{resolveLocalizedText(title, ctx.locale)}</h2>
|
|
83
89
|
{children as ReactNode}
|
|
84
90
|
</section>
|
|
85
91
|
);
|
|
86
92
|
},
|
|
87
93
|
text({ title, children }, ctx) {
|
|
94
|
+
const heading = resolveLocalizedText(title, ctx.locale);
|
|
88
95
|
const body = childArray(children)
|
|
89
96
|
.map((child) => ctx.render(child, ctx.width - 2))
|
|
90
97
|
.filter((block) => block.length > 0)
|
|
91
98
|
.join("\n\n");
|
|
92
|
-
return body.length > 0 ? `${
|
|
99
|
+
return body.length > 0 ? `${heading}\n${indentBlock(body, " ")}` : heading;
|
|
93
100
|
},
|
|
94
101
|
});
|
|
95
102
|
Section.displayName = "Section";
|
|
96
103
|
|
|
97
|
-
|
|
98
|
-
|
|
104
|
+
export interface TextProps {
|
|
105
|
+
/** 自由正文原样渲染,不随 locale 自动翻译。 */
|
|
106
|
+
children: string | number;
|
|
107
|
+
className?: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** 自由文本的显式载体:web 面负责转义,text 面按显示宽度折行。 */
|
|
111
|
+
export const Text = defineComponent<TextProps>({
|
|
99
112
|
web({ children, className }) {
|
|
100
|
-
return <p className={cx("nre", "nre-text", className)}>{children
|
|
113
|
+
return <p className={cx("nre", "nre-text", className)}>{children}</p>;
|
|
101
114
|
},
|
|
102
115
|
text({ children }, ctx) {
|
|
103
|
-
return wrapDisplay(
|
|
116
|
+
return wrapDisplay(String(children), ctx.width).join("\n");
|
|
104
117
|
},
|
|
105
118
|
});
|
|
106
119
|
Text.displayName = "Text";
|
|
120
|
+
Text[COMPONENT_RAW_CHILDREN] = true;
|
|
107
121
|
|
|
108
122
|
export interface StyleProps {
|
|
109
|
-
children
|
|
123
|
+
children: string;
|
|
110
124
|
}
|
|
111
125
|
|
|
112
|
-
/**
|
|
126
|
+
/**
|
|
127
|
+
* 注入页级全局 CSS:树位置只决定声明顺序,不限定作用域;text 面零输出。
|
|
128
|
+
* 配置对象形态的报告要全站样式优先用外壳 styles,两条通道注入同一增强层。
|
|
129
|
+
*/
|
|
113
130
|
export const Style = defineComponent<StyleProps>({
|
|
114
131
|
web({ children }) {
|
|
115
132
|
return <style>{children}</style>;
|
|
@@ -119,15 +136,116 @@ export const Style = defineComponent<StyleProps>({
|
|
|
119
136
|
},
|
|
120
137
|
});
|
|
121
138
|
Style.displayName = "Style";
|
|
139
|
+
Style[COMPONENT_RAW_CHILDREN] = true;
|
|
140
|
+
|
|
141
|
+
// ───────────────────────── Tabs / Tab ─────────────────────────
|
|
142
|
+
|
|
143
|
+
export interface TabsProps extends LayoutProps {}
|
|
144
|
+
|
|
145
|
+
export interface TabProps extends LayoutProps {
|
|
146
|
+
title: LocalizedText;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
interface TabEntry {
|
|
150
|
+
title: LocalizedText;
|
|
151
|
+
children: ReportNode;
|
|
152
|
+
className?: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Tabs 的直接 Tab 子项(数组 / Fragment 已展平;结构合法性由树校验保证)。 */
|
|
156
|
+
function tabEntries(children: ReportNode): TabEntry[] {
|
|
157
|
+
const out: TabEntry[] = [];
|
|
158
|
+
const visit = (node: ReportNode): void => {
|
|
159
|
+
if (node === null || node === undefined || typeof node === "boolean") return;
|
|
160
|
+
if (Array.isArray(node)) {
|
|
161
|
+
for (const child of node) visit(child);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (typeof node === "object" && node !== null && "props" in node) {
|
|
165
|
+
const element = node as { type: unknown; props: Record<string, unknown> };
|
|
166
|
+
if (element.type === Symbol.for("react.fragment")) {
|
|
167
|
+
visit(element.props.children as ReportNode);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
out.push({
|
|
171
|
+
title: (element.props.title as LocalizedText) ?? "",
|
|
172
|
+
children: element.props.children as ReportNode,
|
|
173
|
+
className: element.props.className as string | undefined,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
visit(children);
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 页内并列视图的可切换块。tab 是页内浏览状态,不是数据边界,也不是宿主寻址单位——
|
|
183
|
+
* 两个渲染面都输出全部 tab 的完整内容:web 静态 HTML 每 tab 一个 <details>(首个默认展开,
|
|
184
|
+
* 渐进增强升级成单选 tab 条,切换不改变数据);text 面按声明序输出带标题分节,不折成索引
|
|
185
|
+
* 也不省略(tab 没有选择器,索引只能是死路)。
|
|
186
|
+
*/
|
|
187
|
+
export const Tabs = defineComponent<TabsProps>({
|
|
188
|
+
web({ children, className }, ctx) {
|
|
189
|
+
const tabs = tabEntries(children);
|
|
190
|
+
return (
|
|
191
|
+
<div className={cx("nre", "nre-tabs", className)} data-nre-tabs>
|
|
192
|
+
{tabs.map((tab, i) => (
|
|
193
|
+
<details key={i} className={cx("nre-tab", tab.className)} open={i === 0}>
|
|
194
|
+
<summary className="nre-tab-title">{resolveLocalizedText(tab.title, ctx.locale)}</summary>
|
|
195
|
+
<div className="nre-tab-body">{tab.children as ReactNode}</div>
|
|
196
|
+
</details>
|
|
197
|
+
))}
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
},
|
|
201
|
+
text({ children }, ctx) {
|
|
202
|
+
const tabs = tabEntries(children);
|
|
203
|
+
return tabs
|
|
204
|
+
.map((tab) => {
|
|
205
|
+
const heading = resolveLocalizedText(tab.title, ctx.locale);
|
|
206
|
+
const body = childArray(tab.children)
|
|
207
|
+
.map((child) => ctx.render(child, ctx.width - 2))
|
|
208
|
+
.filter((block) => block.length > 0)
|
|
209
|
+
.join("\n\n");
|
|
210
|
+
return body.length > 0 ? `${heading}\n${indentBlock(body, " ")}` : heading;
|
|
211
|
+
})
|
|
212
|
+
.join("\n\n");
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
Tabs.displayName = "Tabs";
|
|
216
|
+
Tabs[COMPONENT_ROLE] = "tabs";
|
|
217
|
+
|
|
218
|
+
/** 只能直接放在 <Tabs> 下;除通用 children / className 外只有 title。不参与路由,没有 id。 */
|
|
219
|
+
export const Tab = defineComponent<TabProps>({
|
|
220
|
+
web({ children, className }) {
|
|
221
|
+
return <div className={cx("nre", "nre-tab-body", className)}>{children as ReactNode}</div>;
|
|
222
|
+
},
|
|
223
|
+
text({ children }, ctx) {
|
|
224
|
+
return childArray(children)
|
|
225
|
+
.map((child) => ctx.render(child))
|
|
226
|
+
.filter((block) => block.length > 0)
|
|
227
|
+
.join("\n\n");
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
Tab.displayName = "Tab";
|
|
231
|
+
Tab[COMPONENT_ROLE] = "tab";
|
|
232
|
+
|
|
233
|
+
// ───────────────────────── Table ─────────────────────────
|
|
122
234
|
|
|
123
235
|
/** 一列的定义:取哪个 cells 键、表头写什么、往哪边对齐。 */
|
|
124
236
|
export interface TableColumn {
|
|
125
237
|
/** 取 `row.cells[key]` 的键。 */
|
|
126
238
|
key: string;
|
|
127
|
-
/**
|
|
128
|
-
header:
|
|
239
|
+
/** 表头文案,按渲染 locale 选择。 */
|
|
240
|
+
header: LocalizedText;
|
|
129
241
|
/** 对齐方向,默认 `"left"`;`"right"` 按显示宽度右对齐,数字列用。 */
|
|
130
242
|
align?: ColumnAlign;
|
|
243
|
+
/**
|
|
244
|
+
* text 面:单元格折行后的最大物理行数,放不下的部分以 `…` 收口;省略则不限行数。
|
|
245
|
+
* 摘要类列(如比较列表的 Result)用它保证「格子是可扫读的预览」;完整值在下钻面。
|
|
246
|
+
* web 面不消费——网页的高度约束是组件自己的 CSS 决定。
|
|
247
|
+
*/
|
|
248
|
+
maxLines?: number;
|
|
131
249
|
}
|
|
132
250
|
|
|
133
251
|
/** 一行的数据:身份键、已格式化的格子、可选的 attempt locator。 */
|
|
@@ -135,16 +253,16 @@ export interface TableRow {
|
|
|
135
253
|
/** 行身份。 */
|
|
136
254
|
key: string;
|
|
137
255
|
/** 已格式化的显示值;`null`(或缺这个键)渲染成 `—`,不补 0。 */
|
|
138
|
-
cells: Record<string, string | null
|
|
256
|
+
cells: Readonly<Record<string, string | null>>;
|
|
139
257
|
/** 带上就多一列 attempt:web 面链到证据室,text 面列出 locator。 */
|
|
140
258
|
locator?: AttemptLocator;
|
|
141
259
|
}
|
|
142
260
|
|
|
143
261
|
export interface TableProps {
|
|
144
|
-
/**
|
|
145
|
-
columns: TableColumn[];
|
|
262
|
+
/** 非空列定义;数组顺序即渲染顺序。 */
|
|
263
|
+
columns: readonly [TableColumn, ...TableColumn[]];
|
|
146
264
|
/** 行数据;数组顺序即渲染顺序,组件不重排也不过滤。 */
|
|
147
|
-
rows: TableRow[];
|
|
265
|
+
rows: readonly TableRow[];
|
|
148
266
|
/** 组件自带文案(attempt 表头、丢列提示)的语言;省略时随宿主。 */
|
|
149
267
|
locale?: ReportLocale;
|
|
150
268
|
/** web 面挂到 `<table>` 上。 */
|
|
@@ -153,6 +271,34 @@ export interface TableProps {
|
|
|
153
271
|
|
|
154
272
|
const MISSING_MARK = "—";
|
|
155
273
|
|
|
274
|
+
/** 列 key 唯一、行 cells 不携带未声明 key、空列拒绝——无类型 JS 输入在渲染前同样校验。 */
|
|
275
|
+
function validateTableProps(props: TableProps): void {
|
|
276
|
+
if (!Array.isArray(props.columns) || props.columns.length === 0) {
|
|
277
|
+
throw new Error("Table needs at least one column: pass columns: [{ key, header }] — an empty column list renders nothing readable.");
|
|
278
|
+
}
|
|
279
|
+
const keys = new Set<string>();
|
|
280
|
+
for (const column of props.columns) {
|
|
281
|
+
if (keys.has(column.key)) {
|
|
282
|
+
throw new Error(`Table column key "${column.key}" is declared twice — column keys address row.cells and must be unique. Rename one column.`);
|
|
283
|
+
}
|
|
284
|
+
keys.add(column.key);
|
|
285
|
+
}
|
|
286
|
+
const rowKeys = new Set<string>();
|
|
287
|
+
for (const row of props.rows) {
|
|
288
|
+
if (rowKeys.has(row.key)) {
|
|
289
|
+
throw new Error(`Table row key "${row.key}" is declared twice — row keys are the row identity and must be unique.`);
|
|
290
|
+
}
|
|
291
|
+
rowKeys.add(row.key);
|
|
292
|
+
for (const cellKey of Object.keys(row.cells)) {
|
|
293
|
+
if (!keys.has(cellKey)) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`Table row "${row.key}" has a cell "${cellKey}" that no column declares. Declare the column in columns, or drop the stray cell.`,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
156
302
|
/**
|
|
157
303
|
* 自定义表的标准件:列由报告作者定,格子是算好的显示值,两个面各自排整齐。
|
|
158
304
|
*
|
|
@@ -165,7 +311,9 @@ const MISSING_MARK = "—";
|
|
|
165
311
|
* 这个组件上:自定义表和官方表用同一把尺子。
|
|
166
312
|
*/
|
|
167
313
|
export const Table = defineComponent<TableProps>({
|
|
168
|
-
web(
|
|
314
|
+
web(props, ctx) {
|
|
315
|
+
validateTableProps(props);
|
|
316
|
+
const { columns, rows, locale, className } = props;
|
|
169
317
|
const chrome = locale ?? ctx.locale;
|
|
170
318
|
const hasLocator = rows.some((row) => row.locator !== undefined);
|
|
171
319
|
const alignClass = (align?: ColumnAlign) => (align === "right" ? "nre-align-right" : undefined);
|
|
@@ -175,7 +323,7 @@ export const Table = defineComponent<TableProps>({
|
|
|
175
323
|
<tr>
|
|
176
324
|
{columns.map((column) => (
|
|
177
325
|
<th key={column.key} scope="col" className={alignClass(column.align)}>
|
|
178
|
-
{column.header}
|
|
326
|
+
{resolveLocalizedText(column.header, chrome)}
|
|
179
327
|
</th>
|
|
180
328
|
))}
|
|
181
329
|
{hasLocator ? <th scope="col">{localeText(chrome, "table.attempt")}</th> : null}
|
|
@@ -210,6 +358,9 @@ export const Table = defineComponent<TableProps>({
|
|
|
210
358
|
</table>
|
|
211
359
|
);
|
|
212
360
|
},
|
|
213
|
-
text
|
|
361
|
+
text(props, ctx) {
|
|
362
|
+
validateTableProps(props);
|
|
363
|
+
return renderTableText(props, ctx);
|
|
364
|
+
},
|
|
214
365
|
});
|
|
215
366
|
Table.displayName = "Table";
|
|
@@ -1,23 +1,26 @@
|
|
|
1
|
-
// AttemptList:实体列表的叶子层——每项一个 Attempt
|
|
2
|
-
// (assertions
|
|
3
|
-
//
|
|
1
|
+
// AttemptList:实体列表的叶子层——每项一个 Attempt,固定展示判定、算好的单行结果摘要
|
|
2
|
+
// (failureSummary)与证据引用(locator)。完整 assertions / evidence 只经 locator 下钻;
|
|
3
|
+
// 渲染面只做宽度截断,不重算摘要。它不预设只看失败,报告作者过滤 data、用 .slice() 限量,
|
|
4
|
+
// total 让渲染面如实报告剩余数量,不静默截断。
|
|
4
5
|
// ExperimentList / EvalList 的下钻数组是同一个 AttemptListItem[],这里的渲染逻辑因此
|
|
5
6
|
// 也是它们展开区里"逐条 attempt"那一层的唯一实现(通过 AttemptRow 导出复用,不重写一遍)。
|
|
6
7
|
|
|
7
8
|
import type { ReactElement } from "react";
|
|
8
9
|
import type { AttemptListItem } from "../types.ts";
|
|
9
10
|
import type { AttemptLocator } from "../../results/locator.ts";
|
|
10
|
-
import { DEFAULT_REPORT_LOCALE, localeText, type ReportLocale } from "../locale.ts";
|
|
11
|
+
import { DEFAULT_REPORT_LOCALE, countText, localeText, type ReportLocale } from "../locale.ts";
|
|
11
12
|
import { colorClassForKey } from "./colors.ts";
|
|
12
13
|
import { cx, formatDurationMs, formatUSD, verdictMark } from "./format.ts";
|
|
13
14
|
|
|
15
|
+
const DEFAULT_ATTEMPT_HREF = (locator: AttemptLocator): string => `#/attempt/${locator}`;
|
|
16
|
+
|
|
14
17
|
/** locator + 判定符的普通 <a>,AttemptList/EvalList/ExperimentList 共用。 */
|
|
15
18
|
export function AttemptLocatorBadge({
|
|
16
19
|
item,
|
|
17
|
-
attemptHref,
|
|
20
|
+
attemptHref = DEFAULT_ATTEMPT_HREF,
|
|
18
21
|
}: {
|
|
19
22
|
item: Pick<AttemptListItem, "locator" | "verdict">;
|
|
20
|
-
attemptHref
|
|
23
|
+
attemptHref?: (locator: AttemptLocator) => string;
|
|
21
24
|
}): ReactElement {
|
|
22
25
|
return (
|
|
23
26
|
<a
|
|
@@ -30,35 +33,25 @@ export function AttemptLocatorBadge({
|
|
|
30
33
|
);
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
{localeText(locale, "attemptList.score", { score: assertion.score })}
|
|
40
|
-
</span>
|
|
41
|
-
{(assertion.detail || assertion.evidence) && (
|
|
42
|
-
<details className="nre-assertion-more">
|
|
43
|
-
<summary>{localeText(locale, "attemptList.details")}</summary>
|
|
44
|
-
{assertion.detail && <p className="nre-assertion-detail">{assertion.detail}</p>}
|
|
45
|
-
{assertion.evidence && <blockquote className="nre-assertion-evidence">{assertion.evidence}</blockquote>}
|
|
46
|
-
</details>
|
|
47
|
-
)}
|
|
48
|
-
</li>
|
|
49
|
-
);
|
|
36
|
+
/** failureSummary + moreFailures 的展示形态:摘要原样,+N 计数由 moreFailures 驱动。 */
|
|
37
|
+
export function failureSummaryText(item: Pick<AttemptListItem, "failureSummary" | "moreFailures">, locale: ReportLocale): string | null {
|
|
38
|
+
if (item.failureSummary === null) return null;
|
|
39
|
+
return item.moreFailures > 0
|
|
40
|
+
? `${item.failureSummary} · ${countText(locale, "entityList.moreFailures", item.moreFailures)}`
|
|
41
|
+
: item.failureSummary;
|
|
50
42
|
}
|
|
51
43
|
|
|
52
|
-
/** 一条 Attempt
|
|
44
|
+
/** 一条 Attempt 的比较卡片;完整 assertions 通过 locator 下钻,不在列表内展开。 */
|
|
53
45
|
export function AttemptRow({
|
|
54
46
|
item,
|
|
55
|
-
attemptHref,
|
|
47
|
+
attemptHref = DEFAULT_ATTEMPT_HREF,
|
|
56
48
|
locale = DEFAULT_REPORT_LOCALE,
|
|
57
49
|
}: {
|
|
58
50
|
item: AttemptListItem;
|
|
59
|
-
attemptHref
|
|
51
|
+
attemptHref?: (locator: AttemptLocator) => string;
|
|
60
52
|
locale?: ReportLocale;
|
|
61
53
|
}): ReactElement {
|
|
54
|
+
const reason = failureSummaryText(item, locale);
|
|
62
55
|
return (
|
|
63
56
|
<li className={cx("nre-attempt", `nre-attempt-${item.verdict}`)}>
|
|
64
57
|
<div className="nre-attempt-head">
|
|
@@ -68,40 +61,34 @@ export function AttemptRow({
|
|
|
68
61
|
{/* agent 键:稳定散列上色,与其它块(MetricMatrix 列头、DeltaTable 行…)同键同色 */}
|
|
69
62
|
<span className={cx("nre-attempt-agent", "nre-key", colorClassForKey(item.agent))}>{item.agent}</span>
|
|
70
63
|
<span className="nre-attempt-duration">{formatDurationMs(item.durationMs)}</span>
|
|
71
|
-
{
|
|
64
|
+
{/* costUSD 缺失一律 null(测不了),不显示也不伪造 0 */}
|
|
65
|
+
{item.costUSD !== null && <span className="nre-attempt-cost">{formatUSD(item.costUSD)}</span>}
|
|
72
66
|
</div>
|
|
73
|
-
{
|
|
74
|
-
{item.assertions.length > 0 && (
|
|
75
|
-
<ul className="nre-assertions">
|
|
76
|
-
{item.assertions.map((a, i) => (
|
|
77
|
-
<AssertionRow key={i} assertion={a} locale={locale} />
|
|
78
|
-
))}
|
|
79
|
-
</ul>
|
|
80
|
-
)}
|
|
67
|
+
{reason && <p className="nre-attempt-result">{reason}</p>}
|
|
81
68
|
</li>
|
|
82
69
|
);
|
|
83
70
|
}
|
|
84
71
|
|
|
85
72
|
export function AttemptList({
|
|
86
|
-
|
|
73
|
+
data,
|
|
87
74
|
total,
|
|
88
|
-
attemptHref,
|
|
75
|
+
attemptHref = DEFAULT_ATTEMPT_HREF,
|
|
89
76
|
className,
|
|
90
77
|
locale = DEFAULT_REPORT_LOCALE,
|
|
91
78
|
}: {
|
|
92
|
-
|
|
93
|
-
/**
|
|
79
|
+
data: readonly AttemptListItem[];
|
|
80
|
+
/** data 被 slice 时的原始数量;如实显示还剩多少条没展示。 */
|
|
94
81
|
total?: number;
|
|
95
|
-
attemptHref
|
|
82
|
+
attemptHref?: (locator: AttemptLocator) => string;
|
|
96
83
|
className?: string;
|
|
97
84
|
locale?: ReportLocale;
|
|
98
85
|
}): ReactElement {
|
|
99
|
-
const remaining = (total ??
|
|
86
|
+
const remaining = (total ?? data.length) - data.length;
|
|
100
87
|
return (
|
|
101
88
|
<section className={cx("nre", "nre-attempt-list", className)}>
|
|
102
|
-
{
|
|
89
|
+
{data.length === 0 && <p className="nre-attempt-list-empty">{localeText(locale, "attemptList.empty")}</p>}
|
|
103
90
|
<ul className="nre-attempts">
|
|
104
|
-
{
|
|
91
|
+
{data.map((item) => (
|
|
105
92
|
<AttemptRow key={item.locator} item={item} attemptHref={attemptHref} locale={locale} />
|
|
106
93
|
))}
|
|
107
94
|
</ul>
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
// DeltaTable:成对对比(B 相对 A)。每格三个值:A、B、Δ;
|
|
2
|
-
// Δ
|
|
3
|
-
// 任一侧缺数据时 Δ 显示为缺,不硬算(数据侧已给 delta: null
|
|
2
|
+
// Δ 的涨跌好坏由数据侧算好的 outcome 驱动配色(涨不一定是好——成本涨了是坏);
|
|
3
|
+
// 任一侧缺数据时 Δ 显示为缺,不硬算(数据侧已给 delta: null / outcome: "unavailable")。
|
|
4
|
+
// 0 对不是错误:派生形态(pairsByFlag)配不出对时显示明确空态并报告配对域实验数。
|
|
4
5
|
|
|
5
6
|
import type { ReactElement } from "react";
|
|
6
|
-
import type { DeltaData
|
|
7
|
-
import { DEFAULT_REPORT_LOCALE, localeText, resolveMetricLabel, type ReportLocale } from "../locale.ts";
|
|
7
|
+
import type { DeltaData } from "../types.ts";
|
|
8
|
+
import { DEFAULT_REPORT_LOCALE, localeText, resolveLocalizedText, resolveMetricLabel, type ReportLocale } from "../locale.ts";
|
|
8
9
|
import { MetricCellView } from "./cell.tsx";
|
|
9
10
|
import { colorClassForKey } from "./colors.ts";
|
|
10
11
|
import { cx } from "./format.ts";
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return improved ? "nre-delta-good" : "nre-delta-bad";
|
|
19
|
-
}
|
|
13
|
+
const OUTCOME_CLASS: Record<string, string> = {
|
|
14
|
+
improved: "nre-delta-good",
|
|
15
|
+
regressed: "nre-delta-bad",
|
|
16
|
+
unchanged: "nre-delta-flat",
|
|
17
|
+
unavailable: "nre-delta-missing",
|
|
18
|
+
};
|
|
20
19
|
|
|
21
20
|
export function DeltaTable({
|
|
22
21
|
data,
|
|
@@ -27,6 +26,15 @@ export function DeltaTable({
|
|
|
27
26
|
className?: string;
|
|
28
27
|
locale?: ReportLocale;
|
|
29
28
|
}): ReactElement {
|
|
29
|
+
if (data.rows.length === 0) {
|
|
30
|
+
return (
|
|
31
|
+
<div className={cx("nre", "nre-delta-table-empty", className)}>
|
|
32
|
+
<p className="nre-missing">
|
|
33
|
+
{localeText(locale, "delta.empty", { experiments: data.experiments ?? 0 })}
|
|
34
|
+
</p>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
30
38
|
return (
|
|
31
39
|
<table className={cx("nre", "nre-delta-table", className)}>
|
|
32
40
|
<thead>
|
|
@@ -43,39 +51,49 @@ export function DeltaTable({
|
|
|
43
51
|
</tr>
|
|
44
52
|
</thead>
|
|
45
53
|
<tbody>
|
|
46
|
-
{data.rows.map((row) =>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
{row
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
<td key={col.key} className="nre-
|
|
60
|
-
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
54
|
+
{data.rows.map((row) => {
|
|
55
|
+
const label = resolveLocalizedText(row.label, locale);
|
|
56
|
+
return (
|
|
57
|
+
<tr key={row.key}>
|
|
58
|
+
<th scope="row" className="nre-pair">
|
|
59
|
+
{/* pair 的 label:作者声明(或派生规则生成)原样透传;稳定散列上色 */}
|
|
60
|
+
<span className={cx("nre-row-key", "nre-key", colorClassForKey(label))}>{label}</span>
|
|
61
|
+
<span className="nre-pair-ids">
|
|
62
|
+
{row.a.key} → {row.b.key}
|
|
63
|
+
</span>
|
|
64
|
+
</th>
|
|
65
|
+
{data.columns.map((col) => {
|
|
66
|
+
const cell = row.cells[col.key];
|
|
67
|
+
if (!cell) return <td key={col.key} className="nre-td-empty" />;
|
|
68
|
+
return (
|
|
69
|
+
<td key={col.key} className="nre-delta-cell">
|
|
70
|
+
{/* A/B 走统一的 MetricCellView:缺数据文案与覆盖率角标同一套 */}
|
|
71
|
+
<span className="nre-delta-side nre-delta-a">
|
|
72
|
+
<span className="nre-delta-tag">A</span>
|
|
73
|
+
<MetricCellView cell={cell.a} locale={locale} />
|
|
74
|
+
</span>
|
|
75
|
+
<span className="nre-delta-side nre-delta-b">
|
|
76
|
+
<span className="nre-delta-tag">B</span>
|
|
77
|
+
<MetricCellView cell={cell.b} locale={locale} />
|
|
78
|
+
</span>
|
|
79
|
+
<span
|
|
80
|
+
className={cx("nre-delta-side", "nre-delta-d", OUTCOME_CLASS[cell.outcome])}
|
|
81
|
+
data-outcome={cell.outcome}
|
|
82
|
+
>
|
|
83
|
+
<span className="nre-delta-tag">Δ</span>
|
|
84
|
+
{/* 任一侧 null → delta null:显示缺,不硬算 */}
|
|
85
|
+
{cell.delta === null ? (
|
|
86
|
+
<span className="nre-missing">{localeText(locale, "cell.missing")}</span>
|
|
87
|
+
) : (
|
|
88
|
+
resolveLocalizedText(cell.display, locale)
|
|
89
|
+
)}
|
|
90
|
+
</span>
|
|
91
|
+
</td>
|
|
92
|
+
);
|
|
93
|
+
})}
|
|
94
|
+
</tr>
|
|
95
|
+
);
|
|
96
|
+
})}
|
|
79
97
|
</tbody>
|
|
80
98
|
</table>
|
|
81
99
|
);
|
|
Binary file
|