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
package/src/report/text/faces.ts
CHANGED
|
@@ -1,193 +1,221 @@
|
|
|
1
1
|
// 官方组件的 text 面:同一份算好的数据,渲染成终端字符(niceeval show 的形态)。
|
|
2
|
-
//
|
|
3
|
-
// 诚实契约:排序随 better、samples < total 角标、缺数据 — 不补 0、截断报剩余。
|
|
2
|
+
// 与 web 面共守诚实契约:排序随 better、samples < total 角标、缺数据 — 不补 0、截断报剩余。
|
|
4
3
|
// 零 react、零 IO、纯同步 —— 这是 text 宿主不需要 react-dom 的那一半。
|
|
5
|
-
// chrome 文案(注脚、verdict 词、截断提示)经 ctx.locale 查 locale
|
|
6
|
-
//
|
|
4
|
+
// chrome 文案(注脚、verdict 词、截断提示)经 ctx.locale 查 locale 字典;
|
|
5
|
+
// 数据 display 是 LocalizedText,按 LocalizedText 回退规则取值。
|
|
7
6
|
|
|
8
7
|
import type {
|
|
9
8
|
AttemptListItem,
|
|
10
9
|
DeltaData,
|
|
11
10
|
EvalListItem,
|
|
11
|
+
ExperimentComparisonData,
|
|
12
|
+
ExperimentComparisonGroupData,
|
|
12
13
|
ExperimentListItem,
|
|
13
|
-
GroupSummaryData,
|
|
14
14
|
LineData,
|
|
15
15
|
MatrixData,
|
|
16
|
+
MetricCell,
|
|
16
17
|
MetricColumn,
|
|
17
|
-
OverviewData,
|
|
18
18
|
ScatterData,
|
|
19
|
+
ScopeSummaryData,
|
|
19
20
|
ScoreboardData,
|
|
20
21
|
TableData,
|
|
22
|
+
VerdictTally,
|
|
21
23
|
} from "../types.ts";
|
|
22
24
|
import type { TextContext } from "../tree.ts";
|
|
23
25
|
import type { TableColumn, TableRow } from "../primitives.tsx";
|
|
24
26
|
import {
|
|
25
|
-
|
|
27
|
+
experimentDisplayName,
|
|
28
|
+
fitFailureSummary,
|
|
26
29
|
formatDurationMs,
|
|
27
30
|
formatMetricValue,
|
|
28
31
|
formatPlainNumber,
|
|
29
32
|
formatUSD,
|
|
30
33
|
verdictMark,
|
|
31
34
|
} from "../format.ts";
|
|
32
|
-
import {
|
|
33
|
-
|
|
35
|
+
import {
|
|
36
|
+
countText,
|
|
37
|
+
localeText,
|
|
38
|
+
resolveLocalizedText,
|
|
39
|
+
resolveMetricLabel,
|
|
40
|
+
type ReportLocale,
|
|
41
|
+
} from "../locale.ts";
|
|
42
|
+
import { indentBlock, padDisplay, stringWidth, textBar, wrapDisplay } from "./layout.ts";
|
|
34
43
|
import { renderTableText } from "./table.ts";
|
|
35
44
|
import { renderCharPlot, renderCoordinateTable, type PlotPoint } from "./plot.ts";
|
|
36
45
|
|
|
37
46
|
const MISSING_MARK = "—";
|
|
38
47
|
|
|
39
|
-
/** 缺数据文案随 locale(en = "no data"
|
|
48
|
+
/** 缺数据文案随 locale(en = "no data")。 */
|
|
40
49
|
function missingText(locale: ReportLocale): string {
|
|
41
50
|
return localeText(locale, "cell.missing");
|
|
42
51
|
}
|
|
43
52
|
|
|
44
|
-
/** 格子的文本形态:缺数据 —,覆盖不全带 samples/total
|
|
45
|
-
export function cellText(cell:
|
|
53
|
+
/** 格子的文本形态:缺数据 —,覆盖不全带 samples/total 角标;display 按 locale 解析。 */
|
|
54
|
+
export function cellText(cell: MetricCell, locale: ReportLocale): string {
|
|
46
55
|
if (cell.value === null) return MISSING_MARK;
|
|
47
|
-
|
|
56
|
+
const display = resolveLocalizedText(cell.display, locale);
|
|
57
|
+
return cell.samples < cell.total ? `${display} ${cell.samples}/${cell.total}` : display;
|
|
48
58
|
}
|
|
49
59
|
|
|
50
|
-
|
|
60
|
+
/** verdict 计票的紧凑文案("3 passed / 1 failed"):非零判定逐个列,全部为零如实 —。 */
|
|
61
|
+
export function verdictTallyText(tally: VerdictTally, locale: ReportLocale): string {
|
|
62
|
+
const parts: string[] = [];
|
|
63
|
+
for (const kind of ["passed", "failed", "errored", "skipped"] as const) {
|
|
64
|
+
if (tally[kind] > 0) parts.push(`${tally[kind]} ${localeText(locale, `verdict.${kind}`)}`);
|
|
65
|
+
}
|
|
66
|
+
return parts.length > 0 ? parts.join(" / ") : MISSING_MARK;
|
|
67
|
+
}
|
|
51
68
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
countText(locale, "overview.experiments", snapshots.length),
|
|
59
|
-
localeText(locale, "overview.evalsCount", { n: totals.evals }),
|
|
60
|
-
localeText(locale, "overview.attemptsCount", { n: totals.attempts }),
|
|
61
|
-
// 通过率只渲染 computeCell 算好的同一个 MetricCell(cellText 复用缺数据/coverage 角标语义),
|
|
62
|
-
// 不从 passed/failed/errored 现场重算——与 web 面 RunOverview 同一份数据同一种读法。
|
|
63
|
-
`${localeText(locale, "overview.passRate")} ${cellText(totals.passRate)}`,
|
|
64
|
-
countText(locale, "composedFrom", runs),
|
|
65
|
-
...(latest ? [localeText(locale, "latestRun", { run: latest })] : []),
|
|
66
|
-
].join(" · ");
|
|
67
|
-
const tallies = [
|
|
68
|
-
`${localeText(locale, "verdict.passed")} ${totals.passed}`,
|
|
69
|
-
`${localeText(locale, "verdict.failed")} ${totals.failed}`,
|
|
70
|
-
`${localeText(locale, "verdict.errored")} ${totals.errored}`,
|
|
71
|
-
`${localeText(locale, "verdict.skipped")} ${totals.skipped}`,
|
|
72
|
-
totals.costUSD === null ? missingText(locale) : formatUSD(totals.costUSD),
|
|
73
|
-
formatDurationMs(totals.durationMs),
|
|
74
|
-
].join(" · ");
|
|
75
|
-
const lines = [head, tallies];
|
|
76
|
-
for (const warning of data.warnings) lines.push(`! ${warning.message}`);
|
|
77
|
-
return lines.join("\n");
|
|
69
|
+
/** ISO 时间 → "YYYY-MM-DD HH:mm"(本地时区);不可解析原样返回。 */
|
|
70
|
+
function formatDateTimeMinute(iso: string): string {
|
|
71
|
+
const date = new Date(iso);
|
|
72
|
+
if (Number.isNaN(date.valueOf())) return iso;
|
|
73
|
+
const pad = (n: number) => String(n).padStart(2, "0");
|
|
74
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
78
75
|
}
|
|
79
76
|
|
|
80
|
-
// ─────────────────────────
|
|
77
|
+
// ───────────────────────── ScopeSummary ─────────────────────────
|
|
81
78
|
|
|
82
79
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* 不依赖固定网格宽度,窄终端自然换行。
|
|
80
|
+
* 一至两行:头行是端到端成功率(官方 MetricCell,不重算)+ experiment/eval/attempt 数 +
|
|
81
|
+
* `votes` 选中的那级计票 + 总成本;第二行(有则加)是快照时间窗。
|
|
86
82
|
*/
|
|
87
|
-
export function
|
|
83
|
+
export function scopeSummaryText(data: ScopeSummaryData, votes: "eval" | "attempt", ctx: TextContext): string {
|
|
88
84
|
const locale = ctx.locale;
|
|
85
|
+
const tally = votes === "attempt" ? data.attemptVerdicts : data.evalVerdicts;
|
|
89
86
|
const head = [
|
|
90
|
-
`${localeText(locale, "
|
|
87
|
+
`${localeText(locale, "scopeSummary.passRate")} ${cellText(data.endToEndPassRate, locale)}`,
|
|
91
88
|
countText(locale, "overview.experiments", data.experiments),
|
|
92
89
|
localeText(locale, "overview.evalsCount", { n: data.evals }),
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
localeText(locale, "overview.attemptsCount", { n: data.attempts }),
|
|
91
|
+
verdictTallyText(tally, locale),
|
|
92
|
+
`${localeText(locale, "scopeSummary.totalCost")} ${cellText(data.totalCostUSD, locale)}`,
|
|
96
93
|
].join(" · ");
|
|
97
94
|
const lines = [head];
|
|
98
|
-
if (data.
|
|
95
|
+
if (data.range.latestStartedAt !== null) {
|
|
96
|
+
const from = data.range.earliestStartedAt;
|
|
97
|
+
const to = data.range.latestStartedAt;
|
|
98
|
+
lines.push(
|
|
99
|
+
from !== null && from !== to
|
|
100
|
+
? localeText(locale, "scopeSummary.range", { from: formatDateTimeMinute(from), to: formatDateTimeMinute(to) })
|
|
101
|
+
: formatDateTimeMinute(to),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
99
104
|
return lines.join("\n");
|
|
100
105
|
}
|
|
101
106
|
|
|
102
|
-
// ─────────────────────────
|
|
107
|
+
// ───────────────────────── ExperimentComparison ─────────────────────────
|
|
108
|
+
|
|
109
|
+
/** 单组详情:标题行 + 缩进的散点与实验列表(与 web 面同一份预分区数据)。 */
|
|
110
|
+
function comparisonGroupText(group: ExperimentComparisonGroupData, ctx: TextContext): string {
|
|
111
|
+
const inner = Math.max(20, ctx.width - 2);
|
|
112
|
+
const innerCtx: TextContext = {
|
|
113
|
+
width: inner,
|
|
114
|
+
locale: ctx.locale,
|
|
115
|
+
attemptCommand: ctx.attemptCommand,
|
|
116
|
+
experimentCommand: ctx.experimentCommand,
|
|
117
|
+
render: (node, w) => ctx.render(node, w ?? inner),
|
|
118
|
+
};
|
|
119
|
+
const body = [scatterText(group.scatter, innerCtx), experimentListText(group.experiments, innerCtx, group.key)]
|
|
120
|
+
.filter((block) => block.length > 0)
|
|
121
|
+
.join("\n\n");
|
|
122
|
+
return `${localeText(ctx.locale, "experimentComparison.group")} ${group.key}\n\n${indentBlock(body, " ")}`;
|
|
123
|
+
}
|
|
103
124
|
|
|
104
|
-
/**
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
125
|
+
/**
|
|
126
|
+
* text 面:命中多个可比组时只输出组索引与可复制的单组查看命令,不倾倒全部组详情;
|
|
127
|
+
* 命中单组时省略索引,直接输出该组的散点与实验列表。
|
|
128
|
+
*/
|
|
129
|
+
export function experimentComparisonText(
|
|
130
|
+
data: ExperimentComparisonData,
|
|
131
|
+
_className: string | undefined,
|
|
132
|
+
ctx: TextContext,
|
|
108
133
|
): string {
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
134
|
+
const locale = ctx.locale;
|
|
135
|
+
if (data.groups.length === 0) return localeText(locale, "experimentComparison.empty");
|
|
136
|
+
if (data.groups.length === 1) return comparisonGroupText(data.groups[0]!, ctx);
|
|
137
|
+
|
|
138
|
+
const columns: TableColumn[] = [
|
|
139
|
+
{ key: "group", header: localeText(locale, "experimentComparison.group") },
|
|
140
|
+
{ key: "experiments", header: localeText(locale, "scopeSummary.experiments"), align: "right" },
|
|
141
|
+
{ key: "evals", header: localeText(locale, "scopeSummary.evals"), align: "right" },
|
|
142
|
+
{ key: "passRate", header: localeText(locale, "scopeSummary.passRate"), align: "right" },
|
|
143
|
+
{ key: "results", header: localeText(locale, "experimentComparison.results") },
|
|
144
|
+
{ key: "cost", header: localeText(locale, "scopeSummary.totalCost"), align: "right" },
|
|
145
|
+
{ key: "lastRun", header: localeText(locale, "experimentComparison.lastRun") },
|
|
146
|
+
] as unknown as TableColumn[];
|
|
147
|
+
const rows: TableRow[] = data.groups.map((group) => ({
|
|
148
|
+
key: group.key,
|
|
149
|
+
cells: {
|
|
150
|
+
group: group.key,
|
|
151
|
+
experiments: String(group.summary.experiments),
|
|
152
|
+
evals: String(group.summary.evals),
|
|
153
|
+
passRate: cellText(group.summary.endToEndPassRate, locale),
|
|
154
|
+
results: verdictTallyText(group.summary.evalVerdicts, locale),
|
|
155
|
+
cost: group.summary.totalCostUSD.value === null ? null : cellText(group.summary.totalCostUSD, locale),
|
|
156
|
+
lastRun: group.summary.range.latestStartedAt === null ? null : formatDateTimeMinute(group.summary.range.latestStartedAt),
|
|
157
|
+
},
|
|
158
|
+
}));
|
|
159
|
+
const table = renderTableText(
|
|
160
|
+
{ columns: columns as unknown as [TableColumn, ...TableColumn[]], rows, locale },
|
|
161
|
+
ctx,
|
|
162
|
+
);
|
|
163
|
+
const commands = [
|
|
164
|
+
localeText(locale, "experimentComparison.commandsHead"),
|
|
165
|
+
...data.groups.map((group) => ` ${ctx.experimentCommand(group.key)}`),
|
|
166
|
+
].join("\n");
|
|
167
|
+
return `${table}\n\n${commands}`;
|
|
114
168
|
}
|
|
115
169
|
|
|
170
|
+
// ───────────────────────── MetricTable ─────────────────────────
|
|
171
|
+
|
|
116
172
|
export function tableText(data: TableData, ctx: TextContext): string {
|
|
117
173
|
const locale = ctx.locale;
|
|
118
|
-
// meta 在场时补 Model / Agent / Verdicts 列(rows: "experiment" 的榜单 parity);
|
|
119
|
-
// 列序对齐 view 原生榜单:experiment、model、agent、指标列…、verdicts
|
|
120
|
-
const hasMeta = data.rows.some((row) => row.meta !== undefined);
|
|
121
|
-
const hasModel = data.rows.some((row) => row.meta?.model !== undefined);
|
|
122
|
-
const hasVerdicts = data.rows.some((row) => row.meta?.verdicts !== undefined);
|
|
123
174
|
const columns: TableColumn[] = [
|
|
124
|
-
{ key: "dimension", header: data.
|
|
125
|
-
|
|
126
|
-
...(hasMeta ? [{ key: "agent", header: localeText(locale, "table.agent") }] : []),
|
|
127
|
-
// 指标列的键用序号,不用 metric name —— 维度名、"model" 这些键都在同一个命名空间里,
|
|
128
|
-
// 指标恰好叫 "agent" 时不能把 meta 列顶掉。
|
|
175
|
+
{ key: "dimension", header: data.rowDimension },
|
|
176
|
+
// 指标列的键用序号,不用 metric name —— 维度名和指标名在同一个命名空间里,可能撞键。
|
|
129
177
|
...data.columns.map((c, i) => ({ key: `metric${i}`, header: resolveMetricLabel(c.label, locale, c.key) })),
|
|
130
|
-
...(hasVerdicts ? [{ key: "verdicts", header: localeText(locale, "table.verdicts") }] : []),
|
|
131
178
|
];
|
|
132
179
|
const rows: TableRow[] = data.rows.map((row) => {
|
|
133
180
|
const cells: Record<string, string | null> = { dimension: row.key };
|
|
134
|
-
if (hasMeta && hasModel) cells.model = row.meta?.model ?? null;
|
|
135
|
-
if (hasMeta) cells.agent = row.meta?.agent ?? null;
|
|
136
181
|
data.columns.forEach((col, i) => {
|
|
137
|
-
const cell =
|
|
138
|
-
cells[`metric${i}`] = cell ? cellText(cell) : null;
|
|
182
|
+
const cell = row.cells[col.key];
|
|
183
|
+
cells[`metric${i}`] = cell ? cellText(cell, locale) : null;
|
|
139
184
|
});
|
|
140
|
-
if (hasVerdicts) cells.verdicts = row.meta?.verdicts ? verdictTallyText(row.meta.verdicts, locale) : null;
|
|
141
185
|
return { key: row.key, cells };
|
|
142
186
|
});
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// rows: "experiment" 专属的行摘要(eval/attempt 数 + 最后运行时间):表格列已经挤满
|
|
146
|
-
// dimension/model/agent/指标/verdicts,这几个数字挤进行键下面单独一行,与 web 面
|
|
147
|
-
// 「行键下的小 sub-line」同一份信息、同一种「不进列」的取舍。
|
|
148
|
-
const metaLines: string[] = [];
|
|
149
|
-
for (const row of data.rows) {
|
|
150
|
-
if (row.meta?.evals === undefined) continue;
|
|
151
|
-
const parts = [localeText(locale, "overview.evalsCount", { n: row.meta.evals })];
|
|
152
|
-
if (row.meta.attempts !== undefined && row.meta.attempts > row.meta.evals) {
|
|
153
|
-
parts.push(localeText(locale, "overview.attemptsCount", { n: row.meta.attempts }));
|
|
154
|
-
}
|
|
155
|
-
if (row.meta.lastRunAt) parts.push(localeText(locale, "latestRun", { run: row.meta.lastRunAt }));
|
|
156
|
-
metaLines.push(` ${row.key}: ${parts.join(" · ")}`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const blocks = [table, ...(metaLines.length > 0 ? [metaLines.join("\n")] : [])];
|
|
160
|
-
return blocks.join("\n\n");
|
|
187
|
+
return renderTableText({ columns: columns as unknown as [TableColumn, ...TableColumn[]], rows, locale }, ctx);
|
|
161
188
|
}
|
|
162
189
|
|
|
163
190
|
// ───────────────────────── MetricMatrix ─────────────────────────
|
|
164
191
|
|
|
165
192
|
export function matrixText(data: MatrixData, ctx: TextContext): string {
|
|
166
193
|
// 表体全是维度键与 display,没有 chrome 文案;"next:" 是命令提示,不本地化。
|
|
194
|
+
const locale = ctx.locale;
|
|
167
195
|
const rowKeys: string[] = [];
|
|
168
196
|
const columnKeys: string[] = [];
|
|
169
|
-
const byPosition = new Map<string,
|
|
197
|
+
const byPosition = new Map<string, MetricCell>();
|
|
170
198
|
for (const entry of data.cells) {
|
|
171
199
|
if (!rowKeys.includes(entry.row)) rowKeys.push(entry.row);
|
|
172
200
|
if (!columnKeys.includes(entry.column)) columnKeys.push(entry.column);
|
|
173
201
|
byPosition.set(JSON.stringify([entry.row, entry.column]), entry.cell);
|
|
174
202
|
}
|
|
175
203
|
const columns: TableColumn[] = [
|
|
176
|
-
{ key: "dimension", header: data.
|
|
204
|
+
{ key: "dimension", header: data.rowDimension },
|
|
177
205
|
...columnKeys.map((column, i) => ({ key: `column${i}`, header: column })),
|
|
178
206
|
];
|
|
179
207
|
const rows: TableRow[] = rowKeys.map((row) => {
|
|
180
208
|
const cells: Record<string, string | null> = { dimension: row };
|
|
181
209
|
columnKeys.forEach((column, i) => {
|
|
182
210
|
const cell = byPosition.get(JSON.stringify([row, column]));
|
|
183
|
-
cells[`column${i}`] = cell ? cellText(cell) : null; // 稀疏格子在文本里以 — 呈现,不编数
|
|
211
|
+
cells[`column${i}`] = cell ? cellText(cell, locale) : null; // 稀疏格子在文本里以 — 呈现,不编数
|
|
184
212
|
});
|
|
185
213
|
return { key: row, cells };
|
|
186
214
|
});
|
|
187
|
-
const table = renderTableText({ columns, rows, locale
|
|
215
|
+
const table = renderTableText({ columns: columns as unknown as [TableColumn, ...TableColumn[]], rows, locale }, ctx);
|
|
188
216
|
|
|
189
217
|
// 下钻命令:行维度是 eval 时,指向最值得看的一行(先挑有缺格的,再挑按 better 最差的)
|
|
190
|
-
if (data.
|
|
218
|
+
if (data.rowDimension !== "eval" || rowKeys.length === 0) return table;
|
|
191
219
|
const better = data.metric.better ?? "higher";
|
|
192
220
|
let next: string | undefined;
|
|
193
221
|
let worst: { key: string; value: number } | undefined;
|
|
@@ -217,10 +245,11 @@ export function matrixText(data: MatrixData, ctx: TextContext): string {
|
|
|
217
245
|
|
|
218
246
|
const BAR_WIDTH = 20;
|
|
219
247
|
|
|
220
|
-
export function barsText(data: MatrixData): string {
|
|
248
|
+
export function barsText(data: MatrixData, ctx: TextContext): string {
|
|
249
|
+
const locale = ctx.locale;
|
|
221
250
|
const groupKeys: string[] = [];
|
|
222
251
|
const seriesKeys: string[] = [];
|
|
223
|
-
const byPosition = new Map<string,
|
|
252
|
+
const byPosition = new Map<string, MetricCell>();
|
|
224
253
|
for (const entry of data.cells) {
|
|
225
254
|
if (!groupKeys.includes(entry.row)) groupKeys.push(entry.row);
|
|
226
255
|
if (!seriesKeys.includes(entry.column)) seriesKeys.push(entry.column);
|
|
@@ -230,10 +259,9 @@ export function barsText(data: MatrixData): string {
|
|
|
230
259
|
// 条长刻度:% 的天然域是 [0,1],其余以全图最大值为满条
|
|
231
260
|
const values = data.cells.map((c) => c.cell.value).filter((v): v is number => v !== null);
|
|
232
261
|
const maxValue = values.length > 0 ? Math.max(...values) : 0;
|
|
233
|
-
const ratioOf = (value: number) =>
|
|
234
|
-
data.metric.unit === "%" ? value : maxValue === 0 ? 0 : value / maxValue;
|
|
262
|
+
const ratioOf = (value: number) => (data.metric.unit === "%" ? value : maxValue === 0 ? 0 : value / maxValue);
|
|
235
263
|
|
|
236
|
-
const seriesWidth = Math.max(...seriesKeys.map((k) => k
|
|
264
|
+
const seriesWidth = Math.max(...seriesKeys.map((k) => stringWidth(k)), 0);
|
|
237
265
|
const lines: string[] = [];
|
|
238
266
|
for (const group of groupKeys) {
|
|
239
267
|
lines.push(group);
|
|
@@ -256,7 +284,7 @@ export function barsText(data: MatrixData): string {
|
|
|
256
284
|
lines.push(` ${label} ${MISSING_MARK}`);
|
|
257
285
|
continue;
|
|
258
286
|
}
|
|
259
|
-
lines.push(` ${label} ${textBar(ratioOf(cell.value), BAR_WIDTH)} ${cellText(cell)}`);
|
|
287
|
+
lines.push(` ${label} ${textBar(ratioOf(cell.value), BAR_WIDTH)} ${cellText(cell, locale)}`);
|
|
260
288
|
}
|
|
261
289
|
}
|
|
262
290
|
return lines.join("\n");
|
|
@@ -273,14 +301,18 @@ export function scoreboardText(data: ScoreboardData, ctx: TextContext): string {
|
|
|
273
301
|
}
|
|
274
302
|
}
|
|
275
303
|
const columns: TableColumn[] = [
|
|
276
|
-
{ key: "dimension", header: data.
|
|
304
|
+
{ key: "dimension", header: data.rowDimension },
|
|
277
305
|
{ key: "total", header: localeText(locale, "scoreboard.totalText") },
|
|
278
306
|
...subjectKeys.map((key, i) => ({ key: `subject${i}`, header: key })),
|
|
279
307
|
];
|
|
280
308
|
const rows: TableRow[] = data.rows.map((row) => {
|
|
309
|
+
const totalNotes = [
|
|
310
|
+
...(row.total.notRun > 0 ? [localeText(locale, "scoreboard.notRunText", { n: row.total.notRun })] : []),
|
|
311
|
+
...(row.total.unscorable > 0 ? [localeText(locale, "scoreboard.unscorableText", { n: row.total.unscorable })] : []),
|
|
312
|
+
];
|
|
281
313
|
const cells: Record<string, string | null> = {
|
|
282
314
|
dimension: row.key,
|
|
283
|
-
total: `${row.total.display}/${data.fullMarks}`,
|
|
315
|
+
total: `${resolveLocalizedText(row.total.display, locale)}/${data.fullMarks}${totalNotes.length > 0 ? ` ${totalNotes.join(" ")}` : ""}`,
|
|
284
316
|
};
|
|
285
317
|
subjectKeys.forEach((key, i) => {
|
|
286
318
|
const subject = row.subjects.find((s) => s.key === key);
|
|
@@ -289,18 +321,22 @@ export function scoreboardText(data: ScoreboardData, ctx: TextContext): string {
|
|
|
289
321
|
return;
|
|
290
322
|
}
|
|
291
323
|
const score = `${formatPlainNumber(subject.earned)}/${formatPlainNumber(subject.possible)}`;
|
|
292
|
-
|
|
293
|
-
subject.
|
|
294
|
-
|
|
295
|
-
|
|
324
|
+
const notes = [
|
|
325
|
+
...(subject.notRun > 0 ? [localeText(locale, "scoreboard.notRunText", { n: subject.notRun })] : []),
|
|
326
|
+
...(subject.unscorable > 0 ? [localeText(locale, "scoreboard.unscorableText", { n: subject.unscorable })] : []),
|
|
327
|
+
];
|
|
328
|
+
cells[`subject${i}`] = notes.length > 0 ? `${score} ${notes.join(" ")}` : score;
|
|
296
329
|
});
|
|
297
330
|
return { key: row.key, cells };
|
|
298
331
|
});
|
|
299
|
-
const table = renderTableText({ columns, rows, locale }, ctx);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
332
|
+
const table = renderTableText({ columns: columns as unknown as [TableColumn, ...TableColumn[]], rows, locale }, ctx);
|
|
333
|
+
const footnotes: string[] = [];
|
|
334
|
+
if (data.weights.length > 0) {
|
|
335
|
+
const weights = data.weights.map((w) => `${w.prefix} ×${w.weight}`).join(" · ");
|
|
336
|
+
footnotes.push(`${localeText(locale, "scoreboard.weights")} ${weights} · ${localeText(locale, "scoreboard.othersWeight")}`);
|
|
337
|
+
}
|
|
338
|
+
if (data.ignoredEvals > 0) footnotes.push(countText(locale, "scoreboard.ignored", data.ignoredEvals));
|
|
339
|
+
return footnotes.length > 0 ? `${table}\n${footnotes.join("\n")}` : table;
|
|
304
340
|
}
|
|
305
341
|
|
|
306
342
|
// ───────────────────────── MetricScatter ─────────────────────────
|
|
@@ -311,6 +347,26 @@ function axisLabel(col: MetricColumn, locale: ReportLocale): string {
|
|
|
311
347
|
return resolveMetricLabel(col.label, locale, col.key);
|
|
312
348
|
}
|
|
313
349
|
|
|
350
|
+
/**
|
|
351
|
+
* 方向提示:轴向已随 better 反正(lower 反向),「更好」恒指向右上,文案恒为
|
|
352
|
+
* 「越靠右上越好」;仅当 x、y 都声明 better 时显示——任一轴未声明,组件不猜
|
|
353
|
+
* 「更好」朝哪边,整图无提示(返回 undefined)。
|
|
354
|
+
*/
|
|
355
|
+
function betterCornerText(x: MetricColumn, y: MetricColumn, locale: ReportLocale): string | undefined {
|
|
356
|
+
if (x.better === undefined || y.better === undefined) return undefined;
|
|
357
|
+
return localeText(locale, "scatter.betterUpperRight");
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** 轴标题行:"<x label>(越低越好) × <y label>"——better 方向如实标注,不虚构轴向。 */
|
|
361
|
+
function scatterHeading(data: ScatterData, locale: ReportLocale): string {
|
|
362
|
+
const withBetter = (col: MetricColumn) => {
|
|
363
|
+
const label = axisLabel(col, locale);
|
|
364
|
+
if (col.better === undefined) return label;
|
|
365
|
+
return `${label}(${localeText(locale, col.better === "lower" ? "table.lowerBetter" : "table.higherBetter")})`;
|
|
366
|
+
};
|
|
367
|
+
return `${withBetter(data.x)} × ${withBetter(data.y)}`;
|
|
368
|
+
}
|
|
369
|
+
|
|
314
370
|
export function scatterText(data: ScatterData, ctx: TextContext): string {
|
|
315
371
|
const locale = ctx.locale;
|
|
316
372
|
const drawable = data.rows.filter((r) => r.x.value !== null && r.y.value !== null);
|
|
@@ -326,10 +382,14 @@ export function scatterText(data: ScatterData, ctx: TextContext): string {
|
|
|
326
382
|
// 点太密排不下时降级为坐标表,不硬挤
|
|
327
383
|
if (drawable.length > POINT_MARKS.length || ctx.width < 44) {
|
|
328
384
|
const table = renderCoordinateTable(
|
|
329
|
-
drawable.map((r) => ({
|
|
330
|
-
|
|
385
|
+
drawable.map((r) => ({
|
|
386
|
+
key: r.key,
|
|
387
|
+
x: resolveLocalizedText(r.x.display, locale),
|
|
388
|
+
y: resolveLocalizedText(r.y.display, locale),
|
|
389
|
+
})),
|
|
390
|
+
{ key: data.pointDimension, x: axes.x, y: axes.y },
|
|
331
391
|
);
|
|
332
|
-
return [table, ...footnotes].join("\n");
|
|
392
|
+
return [scatterHeading(data, locale), "", table, ...footnotes].join("\n");
|
|
333
393
|
}
|
|
334
394
|
|
|
335
395
|
const points: PlotPoint[] = drawable.map((r, i) => ({
|
|
@@ -347,20 +407,21 @@ export function scatterText(data: ScatterData, ctx: TextContext): string {
|
|
|
347
407
|
}
|
|
348
408
|
for (const list of bySeries.values()) list.sort((a, b) => a.x - b.x);
|
|
349
409
|
|
|
350
|
-
const invertX = data.x.better === "lower";
|
|
351
410
|
const plot = renderCharPlot({
|
|
352
411
|
width: ctx.width,
|
|
353
412
|
points,
|
|
354
413
|
lines: [...bySeries.values()].filter((l) => l.length > 1),
|
|
355
|
-
xLabel:
|
|
356
|
-
yLabel:
|
|
414
|
+
xLabel: axes.x,
|
|
415
|
+
yLabel: axes.y,
|
|
357
416
|
formatX: (v) => formatMetricValue(v, data.x.unit),
|
|
358
417
|
formatY: (v) => formatMetricValue(v, data.y.unit),
|
|
359
|
-
|
|
418
|
+
// 轴方向跟随 better(与 web 面同规则):lower 反向,higher 与未声明正向。
|
|
419
|
+
invertX: data.x.better === "lower",
|
|
360
420
|
invertY: data.y.better === "lower",
|
|
361
421
|
});
|
|
362
422
|
const legend = drawable.map((r, i) => `${POINT_MARKS[i]} ${r.key}`).join(" ");
|
|
363
|
-
|
|
423
|
+
const hint = betterCornerText(data.x, data.y, locale);
|
|
424
|
+
return [scatterHeading(data, locale), plot, "", ...(hint !== undefined ? [hint] : []), legend, ...footnotes].join("\n");
|
|
364
425
|
}
|
|
365
426
|
|
|
366
427
|
// ───────────────────────── MetricLine ─────────────────────────
|
|
@@ -374,6 +435,8 @@ export function lineText(data: LineData, ctx: TextContext): string {
|
|
|
374
435
|
|
|
375
436
|
if (drawable.length === 0) return [missingText(locale), ...footnotes].join("\n");
|
|
376
437
|
|
|
438
|
+
const xLabel = resolveMetricLabel(data.x.label, locale, data.x.key);
|
|
439
|
+
|
|
377
440
|
// 系列 → 字母;无系列 = 单系列
|
|
378
441
|
const seriesKeys: string[] = [];
|
|
379
442
|
for (const r of drawable) {
|
|
@@ -383,8 +446,12 @@ export function lineText(data: LineData, ctx: TextContext): string {
|
|
|
383
446
|
|
|
384
447
|
if (seriesKeys.length > POINT_MARKS.length || ctx.width < 44) {
|
|
385
448
|
const table = renderCoordinateTable(
|
|
386
|
-
drawable.map((r) => ({
|
|
387
|
-
|
|
449
|
+
drawable.map((r) => ({
|
|
450
|
+
key: r.series !== undefined ? `${r.series} (${r.key})` : r.key,
|
|
451
|
+
x: resolveLocalizedText(r.xDisplay, locale),
|
|
452
|
+
y: resolveLocalizedText(r.y.display, locale),
|
|
453
|
+
})),
|
|
454
|
+
{ key: data.seriesDimension ?? xLabel, x: xLabel, y: axisLabel(data.y, locale) },
|
|
388
455
|
);
|
|
389
456
|
return [table, ...footnotes].join("\n");
|
|
390
457
|
}
|
|
@@ -406,11 +473,10 @@ export function lineText(data: LineData, ctx: TextContext): string {
|
|
|
406
473
|
width: ctx.width,
|
|
407
474
|
points,
|
|
408
475
|
lines: lines.filter((l) => l.length > 1),
|
|
409
|
-
xLabel
|
|
476
|
+
xLabel,
|
|
410
477
|
yLabel: axisLabel(data.y, locale),
|
|
411
478
|
formatX: (v) => formatMetricValue(v, data.x.unit),
|
|
412
479
|
formatY: (v) => formatMetricValue(v, data.y.unit),
|
|
413
|
-
invertY: data.y.better === "lower",
|
|
414
480
|
});
|
|
415
481
|
const legend = seriesKeys
|
|
416
482
|
.map((key, i) => `${POINT_MARKS[i]} ${key === "" ? axisLabel(data.y, locale) : key}`)
|
|
@@ -421,41 +487,62 @@ export function lineText(data: LineData, ctx: TextContext): string {
|
|
|
421
487
|
// ───────────────────────── DeltaTable ─────────────────────────
|
|
422
488
|
|
|
423
489
|
export function deltaText(data: DeltaData, ctx: TextContext): string {
|
|
490
|
+
const locale = ctx.locale;
|
|
491
|
+
// 0 对不是错误:明确空态并报告配对域实验数(派生形态携带;字面形态缺省按 0)。
|
|
492
|
+
if (data.rows.length === 0) {
|
|
493
|
+
return localeText(locale, "delta.empty", { experiments: data.experiments ?? 0 });
|
|
494
|
+
}
|
|
424
495
|
const columns: TableColumn[] = [
|
|
425
|
-
{ key: "pair", header: "
|
|
426
|
-
...data.columns.map((c, i) => ({ key: `metric${i}`, header: resolveMetricLabel(c.label,
|
|
496
|
+
{ key: "pair", header: localeText(locale, "delta.pairHeader") },
|
|
497
|
+
...data.columns.map((c, i) => ({ key: `metric${i}`, header: resolveMetricLabel(c.label, locale, c.key) })),
|
|
427
498
|
];
|
|
428
499
|
const rows: TableRow[] = data.rows.map((row) => {
|
|
429
|
-
const cells: Record<string, string | null> = { pair: row.
|
|
500
|
+
const cells: Record<string, string | null> = { pair: resolveLocalizedText(row.label, locale) };
|
|
430
501
|
data.columns.forEach((col, i) => {
|
|
431
|
-
const cell =
|
|
502
|
+
const cell = row.cells[col.key];
|
|
432
503
|
if (!cell) {
|
|
433
504
|
cells[`metric${i}`] = null;
|
|
434
505
|
return;
|
|
435
506
|
}
|
|
436
|
-
const a = cell.a.value === null ? MISSING_MARK : cell.a.display;
|
|
437
|
-
const b = cell.b.value === null ? MISSING_MARK : cell.b.display;
|
|
438
|
-
cells[`metric${i}`] = `${a} → ${b} ${cell.display}`;
|
|
507
|
+
const a = cell.a.value === null ? MISSING_MARK : resolveLocalizedText(cell.a.display, locale);
|
|
508
|
+
const b = cell.b.value === null ? MISSING_MARK : resolveLocalizedText(cell.b.display, locale);
|
|
509
|
+
cells[`metric${i}`] = `${a} → ${b} ${resolveLocalizedText(cell.display, locale)}`;
|
|
439
510
|
});
|
|
440
511
|
return { key: row.key, cells };
|
|
441
512
|
});
|
|
442
|
-
return renderTableText({ columns, rows, locale
|
|
513
|
+
return renderTableText({ columns: columns as unknown as [TableColumn, ...TableColumn[]], rows, locale }, ctx);
|
|
443
514
|
}
|
|
444
515
|
|
|
445
516
|
// ───────────────────────── 实体列表(ExperimentList / EvalList / AttemptList)─────────────────────────
|
|
446
517
|
//
|
|
447
518
|
// 三面共用的紧凑标记:`locator✓`(判定符紧跟 locator,中间不留空格)。
|
|
448
|
-
// ExperimentList / EvalList 逐 attempt 只列这一个标记 +
|
|
449
|
-
// niceeval show 命令;要看某个 attempt 的完整证据,agent 自己拼 `niceeval show <locator
|
|
450
|
-
// 命令模板只在 AttemptList(叶子层)展示完整断言明细时才值得,不在中间层重复。
|
|
519
|
+
// ExperimentList / EvalList 逐 attempt 只列这一个标记 + 各自的摘要,不重复整段
|
|
520
|
+
// niceeval show 命令;要看某个 attempt 的完整证据,agent 自己拼 `niceeval show <locator>`。
|
|
451
521
|
|
|
452
522
|
function locatorBadge(item: { locator: string; verdict: AttemptListItem["verdict"] }): string {
|
|
453
523
|
return `${item.locator}${verdictMark(item.verdict)}`;
|
|
454
524
|
}
|
|
455
525
|
|
|
526
|
+
/**
|
|
527
|
+
* failureSummary + moreFailures 的展示形态:摘要在计算侧已按 Scoring display 契约折好,
|
|
528
|
+
* 这里只加 "+N more failures" 计数与宽度收口,不重算摘要。
|
|
529
|
+
*/
|
|
530
|
+
function attemptReasonText(item: AttemptListItem, locale: ReportLocale, maxChars: number): string | undefined {
|
|
531
|
+
if (item.failureSummary === null) return undefined;
|
|
532
|
+
const withMore =
|
|
533
|
+
item.moreFailures > 0
|
|
534
|
+
? `${item.failureSummary} · ${countText(locale, "entityList.moreFailures", item.moreFailures)}`
|
|
535
|
+
: item.failureSummary;
|
|
536
|
+
return fitFailureSummary(withMore, Math.max(24, maxChars));
|
|
537
|
+
}
|
|
538
|
+
|
|
456
539
|
// ── ExperimentList ──
|
|
457
540
|
|
|
458
|
-
function experimentSummaryTable(
|
|
541
|
+
function experimentSummaryTable(
|
|
542
|
+
items: readonly ExperimentListItem[],
|
|
543
|
+
ctx: TextContext,
|
|
544
|
+
relativeTo?: string,
|
|
545
|
+
): string {
|
|
459
546
|
const locale = ctx.locale;
|
|
460
547
|
const compact = ctx.width < 100;
|
|
461
548
|
const columns: TableColumn[] = [
|
|
@@ -466,48 +553,62 @@ function experimentSummaryTable(items: ExperimentListItem[], ctx: TextContext):
|
|
|
466
553
|
{ key: "passRate", header: compact && locale === "en" ? "Pass" : localeText(locale, "experimentList.passRate"), align: "right" },
|
|
467
554
|
{ key: "result", header: localeText(locale, "experimentList.result") },
|
|
468
555
|
{ key: "tokens", header: localeText(locale, "experimentList.tokens"), align: "right" },
|
|
469
|
-
{ key: "cost", header:
|
|
556
|
+
{ key: "cost", header: localeText(locale, "experimentList.cost"), align: "right" },
|
|
470
557
|
];
|
|
471
558
|
const rows: TableRow[] = items.map((item) => ({
|
|
472
559
|
key: item.experimentId,
|
|
473
560
|
cells: {
|
|
474
|
-
experiment: item.experimentId,
|
|
561
|
+
experiment: experimentDisplayName(item.experimentId, relativeTo),
|
|
475
562
|
model: item.model ?? localeText(locale, "experimentList.defaultModel"),
|
|
476
563
|
agent: item.agent,
|
|
477
|
-
duration: cellText(item.
|
|
478
|
-
passRate: cellText(item.
|
|
479
|
-
result: verdictTallyText(item.
|
|
480
|
-
tokens: cellText(item.tokens),
|
|
481
|
-
cost: cellText(item.
|
|
564
|
+
duration: cellText(item.durationMs, locale),
|
|
565
|
+
passRate: cellText(item.endToEndPassRate, locale),
|
|
566
|
+
result: verdictTallyText(item.evalVerdicts, locale),
|
|
567
|
+
tokens: cellText(item.tokens, locale),
|
|
568
|
+
cost: cellText(item.costUSD, locale),
|
|
482
569
|
},
|
|
483
570
|
}));
|
|
484
571
|
const metadata = items.flatMap((item) =>
|
|
485
572
|
wrapDisplay(
|
|
486
|
-
`${item.experimentId}: ${localeText(locale, "overview.evalsCount", { n: item.evals })} · ${localeText(locale, "overview.attemptsCount", { n: item.attempts })} · ${item.lastRunAt}`,
|
|
573
|
+
`${experimentDisplayName(item.experimentId, relativeTo)}: ${localeText(locale, "overview.evalsCount", { n: item.evals })} · ${localeText(locale, "overview.attemptsCount", { n: item.attempts })} · ${item.lastRunAt}`,
|
|
487
574
|
Math.max(8, ctx.width - 2),
|
|
488
575
|
).map((line) => ` ${line}`),
|
|
489
576
|
);
|
|
490
|
-
return [renderTableText({ columns, rows, locale }, ctx), metadata.join("\n")].join("\n");
|
|
577
|
+
return [renderTableText({ columns: columns as unknown as [TableColumn, ...TableColumn[]], rows, locale }, ctx), metadata.join("\n")].join("\n");
|
|
491
578
|
}
|
|
492
579
|
|
|
493
|
-
function experimentDetailTable(item: ExperimentListItem, ctx: TextContext): string {
|
|
580
|
+
function experimentDetailTable(item: ExperimentListItem, ctx: TextContext, relativeTo?: string): string {
|
|
494
581
|
const locale = ctx.locale;
|
|
495
582
|
const columns: TableColumn[] = [
|
|
496
583
|
{ key: "status", header: localeText(locale, "experimentList.status") },
|
|
497
584
|
{ key: "entity", header: localeText(locale, "experimentList.evalAttempt") },
|
|
498
|
-
|
|
585
|
+
// Result 是可扫读的失败预览,不是证据面:两行放不下的以 … 收口,完整值走 locator 下钻。
|
|
586
|
+
{ key: "result", header: localeText(locale, "experimentList.result"), maxLines: 2 },
|
|
499
587
|
{ key: "duration", header: localeText(locale, "experimentList.duration"), align: "right" },
|
|
500
588
|
{ key: "cost", header: localeText(locale, "experimentList.cost"), align: "right" },
|
|
501
589
|
];
|
|
590
|
+
// Result 的字符预算 ≈ 两行 × 它能分到的列宽(总宽减其它列的自然宽与列距)。这里只做
|
|
591
|
+
// 粗预算;精确的按宽度收口由列的 maxLines 兜底。
|
|
592
|
+
const statusWidth = Math.max(
|
|
593
|
+
stringWidth(localeText(locale, "experimentList.status")),
|
|
594
|
+
...item.evalRows.map((row) => stringWidth(`${verdictMark(row.verdict)} ${localeText(locale, `verdict.${row.verdict}`)}`)),
|
|
595
|
+
);
|
|
596
|
+
const entityWidth = Math.max(
|
|
597
|
+
stringWidth(localeText(locale, "experimentList.evalAttempt")),
|
|
598
|
+
...item.evalRows.flatMap((row) => [stringWidth(row.evalId), ...row.attempts.map((a) => stringWidth(a.locator) + 3)]),
|
|
599
|
+
);
|
|
600
|
+
const fixedWidth = statusWidth + entityWidth + 8 /* duration */ + 6 /* cost */ + 3 * 4; /* 4 段列距 */
|
|
601
|
+
const resultBudget = Math.max(24, (ctx.width - fixedWidth) * 2);
|
|
502
602
|
const rows: TableRow[] = item.evalRows.flatMap((row) => {
|
|
603
|
+
// Eval 父行只承载折叠判定与题级聚合;失败摘要只在 Attempt 子行出现。
|
|
503
604
|
const parent: TableRow = {
|
|
504
605
|
key: row.evalId,
|
|
505
606
|
cells: {
|
|
506
607
|
status: `${verdictMark(row.verdict)} ${localeText(locale, `verdict.${row.verdict}`)}`,
|
|
507
608
|
entity: row.evalId,
|
|
508
609
|
result: "",
|
|
509
|
-
duration: "",
|
|
510
|
-
cost: "",
|
|
610
|
+
duration: localeText(locale, "entityList.average", { value: cellText(row.durationMs, locale) }),
|
|
611
|
+
cost: localeText(locale, "entityList.average", { value: cellText(row.costUSD, locale) }),
|
|
511
612
|
},
|
|
512
613
|
};
|
|
513
614
|
const attempts: TableRow[] = row.attempts.map((attempt, index) => ({
|
|
@@ -515,43 +616,59 @@ function experimentDetailTable(item: ExperimentListItem, ctx: TextContext): stri
|
|
|
515
616
|
cells: {
|
|
516
617
|
status: ` ${verdictMark(attempt.verdict)}`,
|
|
517
618
|
entity: `${index === row.attempts.length - 1 ? "└─" : "├─"} ${attempt.locator}`,
|
|
518
|
-
result:
|
|
619
|
+
result: attemptReasonText(attempt, locale, resultBudget) ?? MISSING_MARK,
|
|
519
620
|
duration: attempt.verdict === "skipped" && attempt.durationMs === 0 ? null : formatDurationMs(attempt.durationMs),
|
|
520
|
-
cost: attempt.costUSD ===
|
|
621
|
+
cost: attempt.costUSD === null ? null : formatUSD(attempt.costUSD),
|
|
521
622
|
},
|
|
522
623
|
}));
|
|
523
624
|
return [parent, ...attempts];
|
|
524
625
|
});
|
|
525
626
|
const flags = item.flags && Object.keys(item.flags).length > 0
|
|
526
|
-
? `${localeText(locale, "experimentList.flags")} ${Object.entries(item.flags)
|
|
627
|
+
? `${localeText(locale, "experimentList.flags")} ${Object.entries(item.flags)
|
|
628
|
+
.map(([key, value]) => `${key}=${typeof value === "string" ? value : JSON.stringify(value)}`)
|
|
629
|
+
.join(" · ")}`
|
|
527
630
|
: undefined;
|
|
528
|
-
return [
|
|
631
|
+
return [
|
|
632
|
+
experimentDisplayName(item.experimentId, relativeTo),
|
|
633
|
+
flags,
|
|
634
|
+
renderTableText({ columns: columns as unknown as [TableColumn, ...TableColumn[]], rows, locale }, ctx),
|
|
635
|
+
]
|
|
636
|
+
.filter(Boolean)
|
|
637
|
+
.join("\n");
|
|
529
638
|
}
|
|
530
639
|
|
|
531
|
-
export function experimentListText(items: ExperimentListItem[], ctx: TextContext): string {
|
|
640
|
+
export function experimentListText(items: readonly ExperimentListItem[], ctx: TextContext, relativeTo?: string): string {
|
|
532
641
|
if (items.length === 0) return localeText(ctx.locale, "attemptList.empty");
|
|
533
|
-
return [
|
|
642
|
+
return [
|
|
643
|
+
experimentSummaryTable(items, ctx, relativeTo),
|
|
644
|
+
...items.map((item) => experimentDetailTable(item, ctx, relativeTo)),
|
|
645
|
+
].join("\n\n");
|
|
534
646
|
}
|
|
535
647
|
|
|
536
648
|
// ── EvalList ──
|
|
537
649
|
|
|
538
|
-
function evalListAttemptLine(item: AttemptListItem): string {
|
|
539
|
-
|
|
650
|
+
function evalListAttemptLine(item: AttemptListItem, ctx: TextContext): string {
|
|
651
|
+
// 行式列表同守「Result 最多两行」:预算 = 两行终端宽,超出按尾截收口。
|
|
652
|
+
const reason = attemptReasonText(item, ctx.locale, ctx.width * 2 - stringWidth(locatorBadge(item)) - 6);
|
|
540
653
|
return ` ${locatorBadge(item)}${reason ? ` · ${reason}` : ""}`;
|
|
541
654
|
}
|
|
542
655
|
|
|
543
|
-
export function evalListText(items: EvalListItem[], ctx: TextContext): string {
|
|
656
|
+
export function evalListText(items: readonly EvalListItem[], ctx: TextContext): string {
|
|
544
657
|
const locale = ctx.locale;
|
|
545
658
|
if (items.length === 0) return localeText(locale, "attemptList.empty");
|
|
546
659
|
const blocks = items.map((item) => {
|
|
547
660
|
const identity = `${item.evalId} · ${item.experimentId} · ${localeText(locale, `verdict.${item.verdict}`)}`;
|
|
548
661
|
const summary = [
|
|
549
|
-
localeText(locale, "attemptList.score", { score: cellText(item.
|
|
662
|
+
localeText(locale, "attemptList.score", { score: cellText(item.examScore, locale) }),
|
|
550
663
|
localeText(locale, "overview.attemptsCount", { n: item.attempts.length }),
|
|
551
|
-
|
|
552
|
-
|
|
664
|
+
localeText(locale, "entityList.average", {
|
|
665
|
+
value: item.durationMs.value === null ? missingText(locale) : formatDurationMs(item.durationMs.value),
|
|
666
|
+
}),
|
|
667
|
+
localeText(locale, "entityList.average", {
|
|
668
|
+
value: item.costUSD.value === null ? missingText(locale) : formatUSD(item.costUSD.value),
|
|
669
|
+
}),
|
|
553
670
|
].join(" · ");
|
|
554
|
-
const attemptLines = item.attempts.map(evalListAttemptLine);
|
|
671
|
+
const attemptLines = item.attempts.map((attempt) => evalListAttemptLine(attempt, ctx));
|
|
555
672
|
return [identity, ` ${summary}`, ...attemptLines].join("\n");
|
|
556
673
|
});
|
|
557
674
|
return blocks.join("\n\n");
|
|
@@ -559,45 +676,25 @@ export function evalListText(items: EvalListItem[], ctx: TextContext): string {
|
|
|
559
676
|
|
|
560
677
|
// ── AttemptList ──
|
|
561
678
|
|
|
562
|
-
/**
|
|
563
|
-
|
|
564
|
-
function attemptListItemText(item: AttemptListItem, ctx: TextContext, locale: ReportLocale): string {
|
|
679
|
+
/** Attempt 比较卡片:只显示一条主失败摘要(至多两行终端宽);完整 assertions 走 locator 下钻。 */
|
|
680
|
+
function attemptListItemText(item: AttemptListItem, ctx: TextContext): string {
|
|
565
681
|
const head = [
|
|
566
682
|
`${verdictMark(item.verdict)} ${item.locator}`,
|
|
567
683
|
item.evalId,
|
|
568
684
|
item.experimentId,
|
|
569
685
|
formatDurationMs(item.durationMs),
|
|
570
|
-
...(item.costUSD !==
|
|
686
|
+
...(item.costUSD !== null ? [formatUSD(item.costUSD)] : []),
|
|
571
687
|
].join(" · ");
|
|
572
688
|
const lines = [head];
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
}
|
|
576
|
-
for (const assertion of item.assertions) {
|
|
577
|
-
const scoreText =
|
|
578
|
-
assertion.threshold !== undefined
|
|
579
|
-
? `${formatPlainNumber(assertion.score)}/${formatPlainNumber(assertion.threshold)}`
|
|
580
|
-
: formatPlainNumber(assertion.score);
|
|
581
|
-
lines.push(
|
|
582
|
-
` ${assertion.severity} ${assertion.name} · ${localeText(locale, `verdict.${assertion.passed ? "passed" : "failed"}`)}${assertion.severity === "soft" ? ` ${scoreText}` : ""}`,
|
|
583
|
-
);
|
|
584
|
-
if (assertion.detail) lines.push(indentBlock(wrapDisplay(assertion.detail, ctx.width - 4).join("\n"), " "));
|
|
585
|
-
if (assertion.evidence) {
|
|
586
|
-
const limit = Math.max(240, ctx.width * 5);
|
|
587
|
-
const evidence =
|
|
588
|
-
assertion.evidence.length <= limit
|
|
589
|
-
? assertion.evidence
|
|
590
|
-
: `${assertion.evidence.slice(0, limit)}… (${assertion.evidence.length - limit} more chars; open ${item.locator} for full evidence)`;
|
|
591
|
-
lines.push(indentBlock(wrapDisplay(evidence, ctx.width - 6).join("\n"), " "));
|
|
592
|
-
}
|
|
593
|
-
}
|
|
689
|
+
const reason = attemptReasonText(item, ctx.locale, ctx.width * 2 - 4);
|
|
690
|
+
if (reason) lines.push(` ${reason}`);
|
|
594
691
|
return lines.join("\n");
|
|
595
692
|
}
|
|
596
693
|
|
|
597
|
-
export function attemptListText(items: AttemptListItem[], total: number | undefined, ctx: TextContext): string {
|
|
694
|
+
export function attemptListText(items: readonly AttemptListItem[], total: number | undefined, ctx: TextContext): string {
|
|
598
695
|
const locale = ctx.locale;
|
|
599
696
|
if (items.length === 0) return localeText(locale, "attemptList.empty");
|
|
600
|
-
const blocks = items.map((item) => attemptListItemText(item, ctx
|
|
697
|
+
const blocks = items.map((item) => attemptListItemText(item, ctx));
|
|
601
698
|
const remaining = (total ?? items.length) - items.length;
|
|
602
699
|
if (remaining > 0) blocks.push(localeText(locale, "attemptList.truncatedText", { n: remaining }));
|
|
603
700
|
return blocks.join("\n\n");
|