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/view/data.test.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// cases: docs/engineering/unit-tests/reports/cases.md
|
|
1
2
|
// view 数据层(data.ts)的单测:loader 收编到 openResults、统计整体住进报告槽之后,
|
|
2
3
|
// 守护三件事——skipped 三种原因如实进 viewData(producer 感知的 npx 提示)、报告槽是
|
|
3
4
|
// 现刻水位口径(裸跑经 selectCurrentResults 跨快照合成每 experiment × eval 的最新判定,
|
|
@@ -110,7 +111,9 @@ describe("loadViewScan · skipped 三种原因进 viewData", () => {
|
|
|
110
111
|
await mkdir(join(incompleteDir, "q1", "a0"), { recursive: true });
|
|
111
112
|
await writeFile(join(incompleteDir, "q1", "a0", "events.json"), "[]", "utf-8");
|
|
112
113
|
|
|
113
|
-
const
|
|
114
|
+
const scan = await loadViewScan(root);
|
|
115
|
+
const { viewData } = scan;
|
|
116
|
+
const reportHtml = scan.reportPages[0]!.html;
|
|
114
117
|
const byReason = new Map(viewData.skippedRuns!.map((s) => [s.dir, s]));
|
|
115
118
|
expect(viewData.skippedRuns).toHaveLength(4);
|
|
116
119
|
|
|
@@ -160,7 +163,9 @@ describe("loadViewScan · 报告槽是现刻水位口径,裸跑与局部收窄
|
|
|
160
163
|
res("q1", "passed"),
|
|
161
164
|
]);
|
|
162
165
|
|
|
163
|
-
const
|
|
166
|
+
const scan = await loadViewScan(root);
|
|
167
|
+
const { viewData } = scan;
|
|
168
|
+
const reportHtml = scan.reportPages[0]!.html;
|
|
164
169
|
// 现刻水位:q1 取周二(更新,通过),q2 取周一(仅此一次,通过)—— 两题全过(2/2 = 100%),
|
|
165
170
|
// 不是「只看周二快照的 1/1」。深链分别指向各自的贡献快照(AttemptLocator 由身份元组——
|
|
166
171
|
// 含快照 startedAt——确定性派生,两个不同快照的 q1/q2 编出两个不同的 locator)。
|
|
@@ -250,7 +255,9 @@ describe("loadViewScan · 新布局落盘直接可读(写入面 / 读取面同
|
|
|
250
255
|
],
|
|
251
256
|
);
|
|
252
257
|
|
|
253
|
-
const
|
|
258
|
+
const scan = await loadViewScan(root);
|
|
259
|
+
const { viewData } = scan;
|
|
260
|
+
const reportHtml = scan.reportPages[0]!.html;
|
|
254
261
|
// 报告槽(ExperimentComparison 的 web 面):Experiment 工作台行 + 官方通过率格子(1 过 1 败 = 50%)。
|
|
255
262
|
expect(reportHtml.en).toContain("compare/bub");
|
|
256
263
|
expect(reportHtml.en).toContain("50%");
|
package/src/view/data.ts
CHANGED
|
@@ -1,25 +1,37 @@
|
|
|
1
1
|
// view 的数据层:读取经 niceeval/results 的 openResults(布局/版本知识只住在那)。
|
|
2
|
-
// 这里只做编排:报告槽
|
|
3
|
-
// 两扇门判定不分叉)、快照明细注入(locator / artifactBase)、skipped
|
|
4
|
-
// (
|
|
5
|
-
// --report 只换报告定义,注入的
|
|
2
|
+
// 这里只做编排:报告槽 Scope 恒经 selectCurrentResults(现刻水位;与 show 调同一个函数,
|
|
3
|
+
// 两扇门判定不分叉)、快照明细注入(locator / artifactBase)、skipped 透传、报告装载与逐页渲染
|
|
4
|
+
// (裸跑填充 niceeval/report/built-in 的默认导出,--report 整槽替换,en / zh-CN 双语各渲染一遍)。
|
|
5
|
+
// --report 只换报告定义,注入的 Scope 与裸跑同一份。统计口径整体住在报告页里
|
|
6
6
|
// (报告组件的官方计算函数),viewData 不再携带 overview / 榜单这类统计产物,
|
|
7
7
|
// 见 docs/feature/reports/view.md「打开与收窄」。
|
|
8
8
|
|
|
9
|
-
import { statSync } from "node:fs";
|
|
9
|
+
import { readFileSync, statSync } from "node:fs";
|
|
10
10
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
11
11
|
import { dedupeAttempts, openResults } from "../results/index.ts";
|
|
12
|
-
import type { AttemptHandle, Results,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
import type { AttemptHandle, Results, Scope, Snapshot, SkippedDir } from "../results/index.ts";
|
|
13
|
+
import {
|
|
14
|
+
loadHostReport,
|
|
15
|
+
renderHostPageHtml,
|
|
16
|
+
reportMetaFor,
|
|
17
|
+
resolveReportTitle,
|
|
18
|
+
type HostReport,
|
|
19
|
+
type HostReportAsset,
|
|
20
|
+
} from "../show/report-host.ts";
|
|
16
21
|
import { selectCurrentResults, filterExperiments } from "../results/select.ts";
|
|
17
22
|
import type { EvalResult } from "../types.ts";
|
|
18
|
-
import type {
|
|
23
|
+
import type {
|
|
24
|
+
SkippedRunNotice,
|
|
25
|
+
ViewData,
|
|
26
|
+
ViewEvalResult,
|
|
27
|
+
ViewReportMeta,
|
|
28
|
+
ViewReportPageHtml,
|
|
29
|
+
ViewSnapshot,
|
|
30
|
+
} from "./shared/types.ts";
|
|
19
31
|
import { t } from "../i18n/index.ts";
|
|
20
32
|
import { RESULTS_SCHEMA_VERSION } from "../types.ts";
|
|
21
33
|
|
|
22
|
-
export type {
|
|
34
|
+
export type { ViewReportMeta, ViewReportPageHtml } from "./shared/types.ts";
|
|
23
35
|
|
|
24
36
|
export interface ViewScan {
|
|
25
37
|
viewData: ViewData;
|
|
@@ -36,22 +48,38 @@ export interface ViewScan {
|
|
|
36
48
|
*/
|
|
37
49
|
attemptsByBase: Map<string, AttemptHandle>;
|
|
38
50
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
51
|
+
* 报告页:每页渲染成静态 HTML(en / zh-CN 各一份),裸跑填充内建报告(单页 id `report`),
|
|
52
|
+
* --report 整槽替换(树 / 配置对象形态都规范化成页列表)。作为 <template
|
|
53
|
+
* id="niceeval-report-<pageId>-<locale>"> 静态块烘进页面(与 __NICEEVAL_VIEW_DATA__ 相邻),
|
|
54
|
+
* HTML 本体不进 viewData —— 前端只负责把当前页 / 当前界面语言对应的块摆进报告槽位置,不解析。
|
|
43
55
|
*/
|
|
44
|
-
|
|
56
|
+
reportPages: ViewReportPageHtml[];
|
|
57
|
+
/** 外壳注入资产(styles / scripts;{src} 已按路径纪律解析成 inline 内容),只进 web 面。 */
|
|
58
|
+
shellAssets: { styles: string[]; scripts: string[] };
|
|
59
|
+
/**
|
|
60
|
+
* --out 的数据等级(见 docs/feature/reports/view.md「静态导出」):全部选中快照带
|
|
61
|
+
* publish:{redaction:"applied"} 才是 "applied",否则 "sensitive"(含本地事实根与
|
|
62
|
+
* redaction:"none"——上游声明过原文发布也不豁免导出时的确认)。
|
|
63
|
+
*/
|
|
64
|
+
publishState: "applied" | "sensitive";
|
|
45
65
|
}
|
|
46
66
|
|
|
47
|
-
/** view 宿主输入的组合语义(与 show 对齐,docs/feature/reports/architecture.md「
|
|
67
|
+
/** view 宿主输入的组合语义(与 show 对齐,docs/feature/reports/architecture.md「Scope 是计算入口」)。 */
|
|
48
68
|
export interface ViewScanOptions {
|
|
49
|
-
/** eval id 前缀(位置参数):收窄报告槽
|
|
69
|
+
/** eval id 前缀(位置参数):收窄报告槽 Scope;证据室(快照明细)不收窄,深链恒可达。 */
|
|
50
70
|
patterns?: string[];
|
|
51
|
-
/** experiment id 前缀(--experiment):
|
|
71
|
+
/** experiment id 前缀(--experiment):Scope 只留该实验。 */
|
|
52
72
|
experiment?: string;
|
|
53
73
|
/** --report 报告文件:相对 cwd 的路径。装载失败抛 ReportLoadError(CLI 打印后退出)。 */
|
|
54
74
|
report?: { path: string; cwd: string };
|
|
75
|
+
/** --page:多页报告的初始页 id;未命中任何页按用法错误退出并列出可用页 id。 */
|
|
76
|
+
page?: string;
|
|
77
|
+
/**
|
|
78
|
+
* 单页渲染失败的处置(docs/feature/reports/architecture.md「管线以页为单位执行」):
|
|
79
|
+
* 本地 server 传 "embed"(该页显示完整错误反馈,其它页照常可读);静态导出与启动前预检
|
|
80
|
+
* 缺省 "throw"(任一页失败整体失败,不产出半套站点)。
|
|
81
|
+
*/
|
|
82
|
+
pageFailure?: "throw" | "embed";
|
|
55
83
|
}
|
|
56
84
|
|
|
57
85
|
/** 可预期的用户输入错误:CLI 打一句英文直说问题与下一步,退出码 1,不抛堆栈。 */
|
|
@@ -68,7 +96,7 @@ export interface IncompatibleRun {
|
|
|
68
96
|
/** 用能读这份报告的 niceeval 版本查看的命令;第三方 harness 的落盘拼不出 npx,返回 undefined。 */
|
|
69
97
|
export function incompatibleViewCommand(run: IncompatibleRun): string | undefined {
|
|
70
98
|
if (run.producer && run.producer.name !== "niceeval") return undefined;
|
|
71
|
-
return `npx niceeval@${run.producer?.version ?? "<version>"} view ${run.dir}`;
|
|
99
|
+
return `npx niceeval@${run.producer?.version ?? "<version>"} view --snapshot ${run.dir}/snapshot.json`;
|
|
72
100
|
}
|
|
73
101
|
|
|
74
102
|
/** 版本不匹配的完整提示文案;CLI 单文件模式和目录扫描占位共用。 */
|
|
@@ -200,9 +228,10 @@ export async function loadViewScan(input?: string, opts: ViewScanOptions = {}):
|
|
|
200
228
|
);
|
|
201
229
|
}
|
|
202
230
|
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
|
|
231
|
+
// 报告槽:裸跑装载内建报告默认导出,--report 整槽替换——同一条「装载 → 规范化 → 逐页渲染」
|
|
232
|
+
// 管线(docs/feature/reports/library/shell.md)。报告吃同一份注入 Scope,web 面在计算侧
|
|
233
|
+
// 静态渲染成 HTML(en / zh-CN 各一遍,切界面语言不重算数据)。
|
|
234
|
+
const slot = await renderReportSlot(opts.report, opts.page, results, selection, opts.pageFailure ?? "throw");
|
|
206
235
|
|
|
207
236
|
// 跨快照按身份键去重:--resume 携带的条目在多份落盘里重复,只保留最新快照里的那份
|
|
208
237
|
// (与官方计算函数的聚合口径一致,Runs / Traces 的计数因此不被复印件灌票)。
|
|
@@ -243,7 +272,8 @@ export async function loadViewScan(input?: string, opts: ViewScanOptions = {}):
|
|
|
243
272
|
}
|
|
244
273
|
}
|
|
245
274
|
|
|
246
|
-
// 全局最新快照(跨全部实验):hero
|
|
275
|
+
// 全局最新快照(跨全部实验):hero 的「最近一次运行」时刻从这里取。
|
|
276
|
+
// hero 标题不从这里取——它走标题回退链(resolveReportTitle,进 viewData.report.title)。
|
|
247
277
|
let latestSnapshot: Snapshot | undefined;
|
|
248
278
|
for (const exp of results.experiments) {
|
|
249
279
|
const candidate = exp.snapshots[0];
|
|
@@ -252,16 +282,27 @@ export async function loadViewScan(input?: string, opts: ViewScanOptions = {}):
|
|
|
252
282
|
}
|
|
253
283
|
|
|
254
284
|
const viewData: ViewData = {
|
|
255
|
-
...(latestSnapshot?.name !== undefined ? { name: latestSnapshot.name } : {}),
|
|
256
285
|
...(latestSnapshot ? { lastRunAt: latestSnapshot.startedAt } : {}),
|
|
257
|
-
// 合成
|
|
258
|
-
// 反向引用取——每个 attempt 的 .snapshot 恒指向它真实所在的贡献快照(无论
|
|
286
|
+
// 合成 Scope 的快照是跨快照拼出来的,来源物理 run 数从 attempt 自己的 snapshot
|
|
287
|
+
// 反向引用取——每个 attempt 的 .snapshot 恒指向它真实所在的贡献快照(无论 Scope
|
|
259
288
|
// 是否合成),所以这条对裸跑与收窄一律成立,不需要分支。
|
|
260
289
|
composedRuns: new Set(selection.snapshots.flatMap((s) => s.attempts.map((a) => a.snapshot.dir))).size,
|
|
261
290
|
snapshots,
|
|
262
291
|
skippedRuns: results.skipped.map(toSkippedNotice),
|
|
292
|
+
report: slot.meta,
|
|
293
|
+
};
|
|
294
|
+
const publishState =
|
|
295
|
+
selection.snapshots.length > 0 && selection.snapshots.every((snap) => snap.publish?.redaction === "applied")
|
|
296
|
+
? ("applied" as const)
|
|
297
|
+
: ("sensitive" as const);
|
|
298
|
+
return {
|
|
299
|
+
viewData,
|
|
300
|
+
artifactDirs,
|
|
301
|
+
attemptsByBase,
|
|
302
|
+
reportPages: slot.pages,
|
|
303
|
+
shellAssets: slot.shellAssets,
|
|
304
|
+
publishState,
|
|
263
305
|
};
|
|
264
|
-
return { viewData, artifactDirs, attemptsByBase, reportHtml };
|
|
265
306
|
}
|
|
266
307
|
|
|
267
308
|
/**
|
|
@@ -286,34 +327,99 @@ export async function loadAttemptIndex(input?: string): Promise<Map<string, Atte
|
|
|
286
327
|
return index;
|
|
287
328
|
}
|
|
288
329
|
|
|
330
|
+
function escapeHtml(text: string): string {
|
|
331
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** 外壳 `{src}` 资产的路径纪律(shell.md「行为约束」):相对报告文件解析;拒绝 `..` 路径段、绝对路径与 `~`。 */
|
|
335
|
+
function resolveShellAssets(assets: HostReportAsset[], baseDir: string | undefined, kind: "styles" | "scripts"): string[] {
|
|
336
|
+
const out: string[] = [];
|
|
337
|
+
for (const asset of assets) {
|
|
338
|
+
if (asset.inline !== undefined) {
|
|
339
|
+
out.push(asset.inline);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const src = asset.src;
|
|
343
|
+
if (src.startsWith("/") || src.startsWith("~") || src.split("/").includes("..")) {
|
|
344
|
+
throw new ViewInputError(
|
|
345
|
+
`Report ${kind} asset "${src}" is not a plain relative path. Assets resolve relative to the report file; ".." segments, absolute paths and "~" are not allowed.`,
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
const abs = resolve(baseDir ?? process.cwd(), src);
|
|
349
|
+
try {
|
|
350
|
+
out.push(readFileSync(abs, "utf-8"));
|
|
351
|
+
} catch {
|
|
352
|
+
throw new ViewInputError(`Report ${kind} asset not found: ${abs} (declared as "${src}").`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return out;
|
|
356
|
+
}
|
|
357
|
+
|
|
289
358
|
/**
|
|
290
|
-
*
|
|
291
|
-
* 经 mtime cache-busting)
|
|
292
|
-
*
|
|
359
|
+
* 报告装载与逐页渲染:装载报告文件(--report;dev server 语义 —— 文件变更下次请求整页重算,
|
|
360
|
+
* 经 mtime cache-busting),缺省装载内建报告默认导出 → 规范化成「外壳 + 非空页列表」→
|
|
361
|
+
* 注入 Scope → 每页 web 面渲染成静态 HTML,en / zh-CN 各渲染一遍(chrome 文案按 locale)。
|
|
362
|
+
* 本地 server 下单页渲染失败折成该页的完整错误反馈块,其它页照常可读(静态导出的
|
|
363
|
+
* 「任一页失败整体失败」由 buildView 侧的 failFast 保证)。
|
|
293
364
|
* react / react-dom 动态加载:data.ts 还被 runner 的续跑携带(loadLatestResultsPerEval)
|
|
294
|
-
* 消费,渲染依赖不进那条路径。attemptHref 缺省即 `#/attempt/@<locator>`
|
|
295
|
-
*
|
|
296
|
-
* 报告槽深链与证据室深链是同一条路由的两个来源。
|
|
365
|
+
* 消费,渲染依赖不进那条路径。attemptHref 缺省即 `#/attempt/@<locator>` 深链路由,
|
|
366
|
+
* 报告页深链与证据室深链是同一条路由的两个来源。
|
|
297
367
|
*/
|
|
298
368
|
async function renderReportSlot(
|
|
299
369
|
report: { path: string; cwd: string } | undefined,
|
|
370
|
+
page: string | undefined,
|
|
300
371
|
results: Results,
|
|
301
|
-
selection:
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
// runtime 走预编译产物(dist/report/**,`pnpm run build:report` 产出),不受 view
|
|
305
|
-
// cwd/tsconfig
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
372
|
+
selection: Scope,
|
|
373
|
+
pageFailure: "throw" | "embed" = "throw",
|
|
374
|
+
): Promise<{ meta: ViewReportMeta; pages: ViewReportPageHtml[]; shellAssets: { styles: string[]; scripts: string[] } }> {
|
|
375
|
+
// 报告 runtime 走预编译产物(dist/report/**,`pnpm run build:report` 产出),不受 view
|
|
376
|
+
// 消费方 cwd/tsconfig 影响;装载与渲染统一经 ../show/report-host.ts(两个宿主共用的联系面)。
|
|
377
|
+
const hostReport: HostReport = await loadHostReport(report?.cwd ?? process.cwd(), report?.path, {
|
|
378
|
+
freshImport: true,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
const initialPageId = page ?? hostReport.pages[0]!.id;
|
|
382
|
+
if (!hostReport.pages.some((p) => p.id === initialPageId)) {
|
|
383
|
+
throw new ViewInputError(
|
|
384
|
+
`error: page "${page}" not found in ${report?.path ?? "the built-in report"}. Available pages: ${hostReport.pages.map((p) => p.id).join(", ")}`,
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const reportDir = report ? dirname(resolve(report.cwd, report.path)) : undefined;
|
|
389
|
+
const shellAssets = {
|
|
390
|
+
styles: resolveShellAssets(hostReport.styles, reportDir, "styles"),
|
|
391
|
+
scripts: resolveShellAssets(hostReport.scripts, reportDir, "scripts"),
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const pages: ViewReportPageHtml[] = [];
|
|
395
|
+
for (const hostPage of hostReport.pages) {
|
|
396
|
+
const meta = reportMetaFor(hostReport, hostPage.id, selection.snapshots);
|
|
397
|
+
const ctx = { scope: selection, results, report: meta };
|
|
398
|
+
try {
|
|
399
|
+
pages.push({
|
|
400
|
+
id: hostPage.id,
|
|
401
|
+
html: {
|
|
402
|
+
en: await renderHostPageHtml(hostPage, ctx, { locale: "en" }),
|
|
403
|
+
"zh-CN": await renderHostPageHtml(hostPage, ctx, { locale: "zh-CN" }),
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
} catch (e) {
|
|
407
|
+
if (pageFailure !== "embed") throw e;
|
|
408
|
+
// 本地 server:该页显示完整错误反馈,其它页照常可读(不让一页的树错误拖垮整站)。
|
|
409
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
410
|
+
const block = `<div class="nre nre-page-error"><pre>${escapeHtml(message)}</pre></div>`;
|
|
411
|
+
pages.push({ id: hostPage.id, html: { en: block, "zh-CN": block } });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const meta: ViewReportMeta = {
|
|
416
|
+
title: resolveReportTitle(hostReport.title, selection.snapshots),
|
|
417
|
+
links: hostReport.links,
|
|
418
|
+
...(hostReport.footer !== undefined ? { footer: hostReport.footer } : {}),
|
|
419
|
+
pages: hostReport.pages.map((p) => ({ id: p.id, title: p.title })),
|
|
420
|
+
initialPageId,
|
|
316
421
|
};
|
|
422
|
+
return { meta, pages, shellAssets };
|
|
317
423
|
}
|
|
318
424
|
|
|
319
425
|
/**
|
package/src/view/index.ts
CHANGED
|
@@ -22,64 +22,54 @@ export {
|
|
|
22
22
|
} from "./data.ts";
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* view
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* - 其余 → eval id 前缀,收窄报告槽 Selection(经 show 同一套 Selection 合成)。
|
|
25
|
+
* view 的输入语义(docs/feature/reports/view.md「打开与收窄」):位置参数只有一种含义——
|
|
26
|
+
* eval id 前缀,与 show 一致,含义不随文件系统状态改变(路径样子的位置参数只会按前缀报无匹配)。
|
|
27
|
+
* 结果根经 `--results <dir>` 递入;单开一份快照经 `--snapshot <file>` 递入,文件不可读时
|
|
28
|
+
* 命令失败(扫描模式对坏快照只跳过)。两个来源互斥。
|
|
30
29
|
*/
|
|
31
30
|
export function resolveViewInput(
|
|
32
31
|
cwd: string,
|
|
33
32
|
positionals: string[],
|
|
34
|
-
|
|
33
|
+
opts: { results?: string; snapshot?: string } = {},
|
|
35
34
|
): { input?: string; patterns: string[] } {
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
const { results, snapshot } = opts;
|
|
36
|
+
if (results !== undefined && snapshot !== undefined) {
|
|
37
|
+
throw new ViewInputError(
|
|
38
|
+
"--results and --snapshot are mutually exclusive: --results scans a results root, --snapshot opens exactly one snapshot file.",
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (results !== undefined) {
|
|
42
|
+
const dir = resolve(cwd, results);
|
|
38
43
|
if (!existsSync(dir)) {
|
|
39
44
|
throw new ViewInputError(`Results directory not found: ${dir}`);
|
|
40
45
|
}
|
|
46
|
+
return { input: dir, patterns: positionals };
|
|
41
47
|
}
|
|
42
|
-
|
|
48
|
+
if (snapshot !== undefined) {
|
|
49
|
+
const file = resolve(cwd, snapshot);
|
|
50
|
+
let isFile = false;
|
|
43
51
|
try {
|
|
44
|
-
|
|
45
|
-
return stat.isFile() ? "file" : stat.isDirectory() ? "dir" : "pattern";
|
|
52
|
+
isFile = statSync(file).isFile();
|
|
46
53
|
} catch {
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
for (const p of positionals) {
|
|
51
|
-
const kind = kindOf(p);
|
|
52
|
-
if (kind === "dir") {
|
|
53
|
-
throw new ViewInputError(
|
|
54
|
-
`"${p}" is a directory. Pass results directories with --run (niceeval view --run ${p}); positional arguments select evals by id prefix.`,
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
if (kind !== "file") continue;
|
|
58
|
-
if (positionals.length > 1) {
|
|
59
|
-
throw new ViewInputError(
|
|
60
|
-
`"${p}" is a summary file, but more positional arguments were given. Single-file mode takes exactly one path: niceeval view ${p}`,
|
|
61
|
-
);
|
|
54
|
+
isFile = false;
|
|
62
55
|
}
|
|
63
|
-
if (
|
|
56
|
+
if (!isFile) {
|
|
64
57
|
throw new ViewInputError(
|
|
65
|
-
|
|
58
|
+
`--snapshot expects a readable snapshot file, got: ${file}. Pass the snapshot.json of one run, or scan a results root with --results <dir>.`,
|
|
66
59
|
);
|
|
67
60
|
}
|
|
68
|
-
return { input:
|
|
61
|
+
return { input: file, patterns: positionals };
|
|
69
62
|
}
|
|
70
|
-
return {
|
|
71
|
-
...(run !== undefined ? { input: resolve(cwd, run) } : {}),
|
|
72
|
-
patterns: positionals,
|
|
73
|
-
};
|
|
63
|
+
return { patterns: positionals };
|
|
74
64
|
}
|
|
75
65
|
|
|
76
66
|
/**
|
|
77
67
|
* 导出静态报告(--out):只有目录式一种形态。写 <dir>/index.html,并把前端会 fetch 的 artifact
|
|
78
|
-
* (sources.json / events.json / trace.json)复制到 <dir>/artifact/<base>/——与本地
|
|
68
|
+
* (sources.json / events.json / trace.json / diff.json)复制到 <dir>/artifact/<base>/——与本地
|
|
79
69
|
* server 的 /artifact/<rel> 路由同一布局,整个目录扔给任何静态托管即是完整体验。
|
|
80
|
-
* 首页即报告槽(
|
|
81
|
-
*
|
|
82
|
-
*
|
|
70
|
+
* 首页即报告槽(裸跑填充内建报告,--report 整槽替换),证据室同站;多页报告仍是单个
|
|
71
|
+
* index.html(页面走 `#/page/<id>` 路由)。单文件(*.html)导出已移除:代码/transcript/trace
|
|
72
|
+
* 视图依赖 artifact 文件,单文件注定残缺(docs/feature/reports/view.md「静态导出」)。
|
|
83
73
|
*/
|
|
84
74
|
export async function buildView(opts: ViewOptions = {}): Promise<string> {
|
|
85
75
|
const out = resolve(opts.out ?? ".niceeval/site");
|
|
@@ -88,19 +78,44 @@ export async function buildView(opts: ViewOptions = {}): Promise<string> {
|
|
|
88
78
|
`--out expects a directory, got "${opts.out}". Single-file HTML export was removed: code, transcript and trace views need artifact files next to the page. Export a directory instead (e.g. --out site) and serve it with any static host.`,
|
|
89
79
|
);
|
|
90
80
|
}
|
|
81
|
+
// --out 与位置参数 / --experiment 互斥(docs/feature/reports/view.md「静态导出」):报告槽收窄
|
|
82
|
+
// 只影响报告,证据室恒随根完整——允许同用会让发布者误以为站点只含该实验,实际根里全部
|
|
83
|
+
// attempt 的证据都已出站。按实验收窄发布 = 用 copySnapshots 构建只含它的发布根,再对新根导出。
|
|
84
|
+
if ((opts.scan?.patterns?.length ?? 0) > 0 || opts.scan?.experiment !== undefined) {
|
|
85
|
+
throw new ViewInputError(
|
|
86
|
+
"--out exports the whole results root and cannot be combined with eval prefixes or --experiment.\n" +
|
|
87
|
+
"To publish a site for one experiment, build a narrower results root and export that:\n" +
|
|
88
|
+
' const results = await openResults(".niceeval");\n' +
|
|
89
|
+
' await copySnapshots(results.latest().filter((s) => s.experimentId.startsWith("<prefix>/")), "<publish-root>", { redact });\n' +
|
|
90
|
+
"Then: niceeval view --results <publish-root> --out <site>",
|
|
91
|
+
);
|
|
92
|
+
}
|
|
91
93
|
const scan = await loadViewScan(opts.input, opts.scan);
|
|
94
|
+
// --out 按发布防呆二分(见 docs/feature/reports/view.md「静态导出」):目标结果根的全部快照
|
|
95
|
+
// 带 publish:{redaction:"applied"}(copySnapshots 补记)时直接导出;redaction:"none"、
|
|
96
|
+
// 无标记结果或本地事实根,都必须显式传 --allow-sensitive-artifacts——静态站原样携带证据文件,
|
|
97
|
+
// 上游声明过原文发布也不豁免这里的确认。
|
|
98
|
+
if (!opts.allowSensitiveArtifacts && scan.publishState !== "applied") {
|
|
99
|
+
throw new ViewInputError(
|
|
100
|
+
`--out refuses to export unredacted results: not every selected snapshot carries publish: { redaction: "applied" }. ` +
|
|
101
|
+
`Produce a publish root first with copySnapshots({ redact }) and export that (niceeval view --results <publish-root> --out <site>), ` +
|
|
102
|
+
`or pass --allow-sensitive-artifacts to explicitly export raw evidence (prompts, tool args, full outputs, sources).`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
92
105
|
await mkdir(out, { recursive: true });
|
|
93
106
|
await writeFile(join(out, "index.html"), await renderHtml(scan), "utf-8");
|
|
94
107
|
await copyFetchedArtifacts(scan, join(out, "artifact"));
|
|
95
108
|
return out;
|
|
96
109
|
}
|
|
97
110
|
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
111
|
+
// 导出没有档位(docs/feature/reports/view.md「静态导出」):结果根里存在且前端会读取的证据
|
|
112
|
+
// 文件全部复制——events.json / trace.json / diff.json 原字节复制(diff 有就带,缺时前端在
|
|
113
|
+
// 证据位置如实显示缺失;体积取舍在构建发布根时用 copySnapshots({ artifacts }) 做,不在导出层);
|
|
114
|
+
// 唯一永不复制的是 o11y.json——报告数字在导出时已烘进 HTML,浏览器不读它。
|
|
115
|
+
// sources.json 是格式例外——盘上是去重后的引用(`{path, sha256}[]`),必须先经
|
|
101
116
|
// AttemptHandle.sources() 解引用出完整内容(`{path, content}[]`)再写出,否则浏览器端的
|
|
102
117
|
// isCodeSource 守卫因缺 content 字段判空,代码视图会误判「源码未捕获」(即便源码明明捕获了)。
|
|
103
|
-
const RAW_COPY_ARTIFACTS = ["events.json", "trace.json"];
|
|
118
|
+
const RAW_COPY_ARTIFACTS = ["events.json", "trace.json", "diff.json"];
|
|
104
119
|
|
|
105
120
|
async function copyFetchedArtifacts(scan: ViewScan, artifactRoot: string): Promise<void> {
|
|
106
121
|
for (const [base, srcDir] of scan.artifactDirs) {
|
package/src/view/server.ts
CHANGED
|
@@ -6,12 +6,15 @@ import { readFile } from "node:fs/promises";
|
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
7
|
import { loadAttemptIndex, loadViewScan, viewRoot, type ViewScan, type ViewScanOptions } from "./data.ts";
|
|
8
8
|
import type { AttemptHandle } from "../results/index.ts";
|
|
9
|
+
import { localizeText } from "../show/report-host.ts";
|
|
9
10
|
import { formatThrown } from "../util.ts";
|
|
10
11
|
|
|
11
12
|
export interface ViewOptions {
|
|
12
13
|
input?: string;
|
|
13
14
|
out?: string;
|
|
14
15
|
port?: number;
|
|
16
|
+
/** `--out` 对非发布根(无 publish:applied 标记)导出时的显式确认;静态站原样携带证据文件。 */
|
|
17
|
+
allowSensitiveArtifacts?: boolean;
|
|
15
18
|
/** 报告槽的组合语义(位置前缀 / --experiment / --report),透传给 loadViewScan。 */
|
|
16
19
|
scan?: ViewScanOptions;
|
|
17
20
|
}
|
|
@@ -31,9 +34,11 @@ const TEMPLATE_PLACEHOLDERS = {
|
|
|
31
34
|
export async function startViewServer(opts: ViewOptions = {}): Promise<ViewServer> {
|
|
32
35
|
const input = opts.input;
|
|
33
36
|
const root = viewRoot(input);
|
|
34
|
-
//
|
|
37
|
+
// 本地 server 的单页失败折成该页的错误块,其它页照常可读(静态导出仍整体失败)。
|
|
38
|
+
const scanOptions = { ...opts.scan, pageFailure: "embed" as const };
|
|
39
|
+
// 数据装载先跑一遍:--snapshot 指向读不了的快照、--report 装载失败、
|
|
35
40
|
// 前缀匹配不到,都要在起 server 前就失败并给出提示。
|
|
36
|
-
await loadViewScan(input,
|
|
41
|
+
await loadViewScan(input, scanOptions);
|
|
37
42
|
const server = createServer(async (req, res) => {
|
|
38
43
|
try {
|
|
39
44
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
@@ -65,7 +70,7 @@ export async function startViewServer(opts: ViewOptions = {}): Promise<ViewServe
|
|
|
65
70
|
});
|
|
66
71
|
// 每次请求现读现算,永远是盘上最新数据;--report 的报告文件变更同样在
|
|
67
72
|
// 下次请求整页重算(装载走 mtime cache-busting,见 report/load.ts)。
|
|
68
|
-
res.end(await renderHtml(await loadViewScan(input,
|
|
73
|
+
res.end(await renderHtml(await loadViewScan(input, scanOptions)));
|
|
69
74
|
} catch (e) {
|
|
70
75
|
res.writeHead(500, { "content-type": "text/plain; charset=utf-8" });
|
|
71
76
|
res.end(formatThrown(e));
|
|
@@ -84,12 +89,13 @@ export async function startViewServer(opts: ViewOptions = {}): Promise<ViewServe
|
|
|
84
89
|
|
|
85
90
|
/**
|
|
86
91
|
* 把 viewData(只含原始值与相对路径,不含宿主机绝对路径)和前端产物烘焙进单个 HTML。
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
92
|
+
* 报告槽恒在:每页报告 HTML 作为 <template id="niceeval-report-<pageId>-<locale>"> 静态块
|
|
93
|
+
* 烘在 __NICEEVAL_VIEW_DATA__ 旁(不 hydrate,自定义组件的 <Style> 产物已内联其中),
|
|
94
|
+
* 并恒内联官方组件样式(report/react/styles.css)与渐进增强 runtime(report/react/enhance.js,
|
|
95
|
+
* 内联 <script>:排序 / 过滤 / tooltip,document 级事件委托,报告块被前端搬进槽位也无需重绑;
|
|
96
|
+
* 无 JS 时报告内容依旧完整);外壳声明的 styles 注入在官方样式之后、scripts 注入在官方
|
|
97
|
+
* 增强脚本之后 </body> 前,均按声明顺序(docs/feature/reports/library/shell.md)。
|
|
98
|
+
* 前端只把当前页 / 当前界面语言对应的块摆进报告槽位置,不解析。
|
|
93
99
|
*/
|
|
94
100
|
export async function renderHtml(scan: ViewScan): Promise<string> {
|
|
95
101
|
const template = await readViewAsset("template.html");
|
|
@@ -100,19 +106,35 @@ export async function renderHtml(scan: ViewScan): Promise<string> {
|
|
|
100
106
|
readFile(new URL("../report/react/enhance.js", import.meta.url), "utf-8"),
|
|
101
107
|
]);
|
|
102
108
|
|
|
109
|
+
const shellStyles = scan.shellAssets.styles.map((css) => `\n<style>\n${css}\n</style>`).join("");
|
|
110
|
+
const shellScripts = scan.shellAssets.scripts.map((js) => `<script>\n${js}\n</script>\n`).join("");
|
|
111
|
+
|
|
112
|
+
const pageTemplates = scan.reportPages
|
|
113
|
+
.flatMap((page) => [
|
|
114
|
+
`<template id="niceeval-report-${page.id}-en">${page.html.en}</template>`,
|
|
115
|
+
`<template id="niceeval-report-${page.id}-zh-CN">${page.html["zh-CN"]}</template>`,
|
|
116
|
+
])
|
|
117
|
+
.join("\n");
|
|
118
|
+
|
|
119
|
+
// 初始 <title> 与 hero 同源:走完回退链的报告标题(viewData.report.title;终点是内置文案
|
|
120
|
+
// 「Eval 运行结果 / Eval Results」)。模板 lang="en",初始按 en 解析;前端按界面语言更新。
|
|
121
|
+
const title = localizeText(scan.viewData.report?.title, "en") ?? "Eval Results";
|
|
122
|
+
|
|
103
123
|
return template
|
|
104
124
|
.replace(
|
|
105
|
-
|
|
106
|
-
() => `<
|
|
125
|
+
/<title>[^<]*<\/title>/,
|
|
126
|
+
() => `<title>${title.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")}</title>`,
|
|
107
127
|
)
|
|
108
128
|
.replace(
|
|
109
|
-
TEMPLATE_PLACEHOLDERS.
|
|
129
|
+
TEMPLATE_PLACEHOLDERS.styles,
|
|
110
130
|
() =>
|
|
111
|
-
`<
|
|
112
|
-
|
|
131
|
+
`<style>\n${styles}\n</style>\n<style>\n${reportStyles}\n</style>\n<script>\n${reportEnhance}\n</script>` +
|
|
132
|
+
shellStyles,
|
|
113
133
|
)
|
|
134
|
+
.replace(TEMPLATE_PLACEHOLDERS.reportSlot, () => pageTemplates)
|
|
114
135
|
.replace(TEMPLATE_PLACEHOLDERS.viewData, () => JSON.stringify(scan.viewData).replace(/</g, "\\u003c"))
|
|
115
|
-
.replace(TEMPLATE_PLACEHOLDERS.appCode, () => JSON.stringify(app).replace(/</g, "\\u003c"))
|
|
136
|
+
.replace(TEMPLATE_PLACEHOLDERS.appCode, () => JSON.stringify(app).replace(/</g, "\\u003c"))
|
|
137
|
+
.replace("</body>", () => `${shellScripts}</body>`);
|
|
116
138
|
}
|
|
117
139
|
|
|
118
140
|
/** rel 的末段文件名;base = 去掉末段后剩下的部分(与 artifactUrl 的按段编码/拼接对称)。 */
|
package/src/view/shared/types.ts
CHANGED
|
@@ -12,11 +12,40 @@ import type { AttemptLocator } from "../../results/locator.ts";
|
|
|
12
12
|
export type { AttemptLocator };
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* 一页报告的双语静态 HTML:同一棵页树按 locale 渲染两遍(en / zh-CN),server 烘成
|
|
16
|
+
* <template id="niceeval-report-<pageId>-<locale>"> 静态块,前端按当前页与界面语言摆放
|
|
17
|
+
* 对应块,切语言 / 切页不重算数据。
|
|
17
18
|
*/
|
|
18
19
|
export type ReportSlotHtml = Record<ReportLocale, string>;
|
|
19
20
|
|
|
21
|
+
/** 服务端渲染好的一页报告(HTML 本体不进 viewData,烘成 <template> 静态块)。 */
|
|
22
|
+
export interface ViewReportPageHtml {
|
|
23
|
+
id: string;
|
|
24
|
+
html: ReportSlotHtml;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 导航里的一页(id = `#/page/<id>` 路由与 `--page` 的取值)。 */
|
|
28
|
+
export interface ViewReportPageMeta {
|
|
29
|
+
id: string;
|
|
30
|
+
title: LocalizedText;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 规范化后的报告外壳声明(docs/feature/reports/library/shell.md):壳(导航 / 页脚)由前端
|
|
35
|
+
* 渲染,页内容消费 <template> 静态块。title 已走完回退链(def.title → 唯一且相同的快照 name →
|
|
36
|
+
* 内置文案「Eval 运行结果 / Eval Results」),落点是首页 hero 与浏览器标题——页头品牌位恒为
|
|
37
|
+
* NiceEval 字标,不归 title;scripts / styles 是注入资产,不进 viewData。
|
|
38
|
+
* link 的 icon 是内联 SVG 字符串(原样透传、原样内联),不收组件——viewData 就是序列化边界。
|
|
39
|
+
*/
|
|
40
|
+
export interface ViewReportMeta {
|
|
41
|
+
title: LocalizedText;
|
|
42
|
+
links: { label: LocalizedText; href: string; icon?: { svg: string } }[];
|
|
43
|
+
footer?: LocalizedText;
|
|
44
|
+
pages: ViewReportPageMeta[];
|
|
45
|
+
/** 初始页(--page 或声明序第一页);`#/page/<id>` 路由覆盖它。 */
|
|
46
|
+
initialPageId: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
20
49
|
/** view 侧的 attempt 结果 = 瘦身后的 EvalResult + loader 注入的深链身份(不透明 AttemptLocator,
|
|
21
50
|
* `#/attempt/@<locator>` 路由的参数,与 Reports 的 MetricCell.refs / `ctx.attemptHref` 同一身份契约)
|
|
22
51
|
* 与 artifact 基址。 */
|
|
@@ -61,14 +90,14 @@ export interface SkippedRunNotice {
|
|
|
61
90
|
* 格式化统一由前端按当前界面 locale 做。
|
|
62
91
|
*/
|
|
63
92
|
export interface ViewData {
|
|
64
|
-
/** 项目名(来自 config.name);hero 标题,可按 locale 多语言。 */
|
|
65
|
-
name?: LocalizedText;
|
|
66
93
|
/** 最近一次 run 的 startedAt(ISO);没有历史 run 时缺省。 */
|
|
67
94
|
lastRunAt?: string;
|
|
68
95
|
/** 报告槽 Selection 合成自几个物理 run;hero「合成来源」标注。 */
|
|
69
96
|
composedRuns: number;
|
|
70
|
-
/** 全部历史快照(跨快照按身份键去重后);修复 prompt 吃 latest,
|
|
97
|
+
/** 全部历史快照(跨快照按身份键去重后);修复 prompt 吃 latest,Attempts / Traces 吃全部。 */
|
|
71
98
|
snapshots: ViewSnapshot[];
|
|
72
99
|
/** 读不了的落盘(三种原因);前端顶部横幅展示,不静默。 */
|
|
73
100
|
skippedRuns?: SkippedRunNotice[];
|
|
101
|
+
/** 报告外壳与页导航的声明(规范化后);缺省时前端按单页 `report` 兜底。 */
|
|
102
|
+
report?: ViewReportMeta;
|
|
74
103
|
}
|