niceeval 0.6.2 → 0.7.1
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 +2 -2
- package/dist/i18n/zh-CN.d.ts +3 -3
- package/dist/report/aggregate.d.ts +32 -26
- package/dist/report/aggregate.js +157 -76
- 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 +91 -164
- package/dist/report/components.js +377 -114
- package/dist/report/compute.d.ts +86 -73
- package/dist/report/compute.js +592 -432
- package/dist/report/flag.d.ts +28 -17
- package/dist/report/flag.js +86 -16
- package/dist/report/format.d.ts +11 -11
- package/dist/report/format.js +17 -15
- package/dist/report/index.d.ts +16 -17
- package/dist/report/index.js +20 -22
- package/dist/report/load.js +3 -2
- package/dist/report/locale.d.ts +49 -34
- package/dist/report/locale.js +106 -58
- package/dist/report/metrics.d.ts +10 -3
- package/dist/report/metrics.js +46 -12
- package/dist/report/primitives.d.ts +42 -15
- package/dist/report/primitives.js +135 -26
- package/dist/report/react/AttemptList.d.ts +10 -8
- package/dist/report/react/AttemptList.js +18 -10
- package/dist/report/react/DeltaTable.js +19 -18
- package/dist/report/react/EvalList.d.ts +3 -3
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentComparison.d.ts +4 -2
- package/dist/report/react/ExperimentComparison.js +5 -4
- package/dist/report/react/ExperimentList.d.ts +3 -3
- package/dist/report/react/ExperimentList.js +16 -15
- 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 +54 -17
- 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/fixtures.d.ts +5 -9
- package/dist/report/react/fixtures.js +105 -149
- package/dist/report/react/index.d.ts +15 -5
- package/dist/report/react/index.js +18 -7
- package/dist/report/report.d.ts +137 -20
- package/dist/report/report.js +261 -34
- package/dist/report/text/faces.d.ts +17 -19
- package/dist/report/text/faces.js +225 -157
- package/dist/report/text/plot.js +1 -1
- package/dist/report/text/table.js +2 -2
- package/dist/report/tree.d.ts +90 -40
- package/dist/report/tree.js +252 -94
- package/dist/report/types.d.ts +245 -300
- 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 +38 -14
- package/dist/shared/aggregate.d.ts +3 -2
- package/dist/shared/aggregate.js +5 -4
- 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 +11 -11
- 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 +5 -5
- package/docs-site/zh/{guides → explanation}/runner.mdx +1 -1
- package/docs-site/zh/{concepts → explanation}/tier.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/agent-feedback-loop.mdx +7 -7
- package/docs-site/zh/{guides → how-to}/authoring.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/connect-otel.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/connect-your-agent.mdx +18 -18
- package/docs-site/zh/{guides → how-to}/custom-reports.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/experiments.mdx +3 -3
- package/docs-site/zh/{guides → how-to}/publish-report.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/sandbox-agent.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/sandbox-providers.mdx +1 -1
- package/docs-site/zh/{guides → how-to}/viewing-results.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/write-experiment.mdx +3 -3
- package/docs-site/zh/{guides → how-to}/write-send.mdx +13 -13
- package/docs-site/zh/index.mdx +23 -25
- package/docs-site/zh/introduction.mdx +8 -8
- package/docs-site/zh/reference/builtin-agents.mdx +5 -5
- package/docs-site/zh/reference/capabilities.mdx +6 -6
- package/docs-site/zh/reference/cli.mdx +9 -7
- package/docs-site/zh/reference/define-agent.mdx +1 -1
- package/docs-site/zh/reference/events.mdx +3 -3
- package/docs-site/zh/{guides → reference}/official-adapters.mdx +7 -7
- package/docs-site/zh/{guides → reference}/report-components.mdx +5 -5
- package/docs-site/zh/{guides → reference}/results-data.mdx +5 -5
- package/docs-site/zh/{guides → troubleshooting}/debug-sandbox.mdx +2 -2
- package/docs-site/zh/{guides → troubleshooting}/debugging.mdx +4 -2
- package/docs-site/zh/{quickstart.mdx → tutorials/quickstart.mdx} +5 -17
- package/package.json +6 -2
- package/src/agents/index.ts +2 -2
- package/src/agents/openai-compat.ts +1 -1
- package/src/agents/streaming.ts +2 -2
- package/src/agents/types.ts +3 -3
- package/src/cli.ts +42 -23
- package/src/context/context.ts +1 -1
- package/src/context/session.test.ts +1 -1
- package/src/context/session.ts +1 -1
- package/src/i18n/en.ts +18 -16
- package/src/i18n/zh-CN.ts +16 -15
- package/src/report/aggregate.ts +175 -87
- package/src/report/built-in/index.tsx +9 -0
- package/src/report/components.tsx +625 -285
- package/src/report/compute.ts +717 -515
- package/src/report/dual-render.test.tsx +738 -1148
- package/src/report/flag.ts +97 -33
- package/src/report/format.ts +18 -22
- package/src/report/index.ts +113 -58
- package/src/report/load.ts +3 -2
- package/src/report/locale.ts +120 -69
- package/src/report/metrics.ts +42 -12
- package/src/report/primitives.tsx +190 -45
- package/src/report/react/AttemptList.tsx +32 -20
- package/src/report/react/DeltaTable.tsx +63 -45
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentComparison.tsx +12 -7
- package/src/report/react/ExperimentList.tsx +38 -26
- 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 +74 -20
- 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/enhance.js +57 -5
- package/src/report/react/fixtures.ts +109 -156
- package/src/report/react/index.tsx +24 -39
- package/src/report/react/render.test.tsx +139 -104
- package/src/report/react/styles.css +181 -91
- package/src/report/report.test.ts +761 -1031
- package/src/report/report.ts +425 -47
- package/src/report/text/faces.ts +257 -164
- package/src/report/text/plot.ts +1 -1
- package/src/report/text/table.ts +2 -2
- package/src/report/tree.ts +362 -104
- package/src/report/types.ts +257 -287
- package/src/report/web.ts +63 -20
- package/src/results/attempt-evidence.test.ts +4 -4
- package/src/results/attempt-evidence.ts +5 -5
- package/src/results/copy.ts +6 -6
- package/src/results/host-equivalence.test.ts +26 -14
- package/src/results/index.ts +10 -4
- package/src/results/open.ts +8 -4
- package/src/results/results.test.ts +4 -3
- package/src/results/select.ts +104 -34
- package/src/results/types.ts +36 -14
- package/src/runner/feedback/human.test.ts +1 -1
- package/src/runner/run.ts +1 -1
- package/src/sandbox/cli-commands.ts +2 -2
- package/src/scoring/judge.test.ts +1 -1
- package/src/shared/aggregate.ts +5 -4
- package/src/show/compose.ts +50 -67
- package/src/show/index.ts +107 -56
- package/src/show/render.ts +43 -27
- package/src/show/report-host.test.ts +188 -0
- package/src/show/report-host.ts +375 -0
- package/src/show/show.test.ts +86 -36
- package/src/view/app/App.test.tsx +69 -0
- package/src/view/app/App.tsx +144 -48
- package/src/view/app/components/AttemptModal.tsx +324 -63
- package/src/view/app/components/CodeView.tsx +10 -4
- package/src/view/app/i18n.ts +31 -17
- package/src/view/app/lib/artifact-url.ts +14 -3
- 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 +21 -1
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +6 -6
- package/src/view/data.test.ts +9 -3
- package/src/view/data.ts +145 -49
- package/src/view/index.ts +48 -44
- package/src/view/server.ts +35 -15
- package/src/view/shared/types.ts +34 -5
- package/src/view/styles.css +224 -0
- package/src/view/view-report.test.ts +161 -57
- package/dist/report/built-ins/experiment-comparison.d.ts +0 -39
- package/dist/report/built-ins/experiment-comparison.js +0 -119
- package/dist/report/built-ins/index.d.ts +0 -2
- 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/src/report/built-in-user-parity.test.tsx +0 -597
- package/src/report/built-ins/experiment-comparison.tsx +0 -179
- package/src/report/built-ins/index.ts +0 -7
- package/src/report/react/GroupSummary.tsx +0 -66
- package/src/report/react/RunOverview.tsx +0 -109
- /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}/ci-integration.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/dataset-fanout.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/fixtures.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/reporters.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/scoring-guide.mdx +0 -0
package/src/show/index.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
// niceeval show —— 终端宿主(行为规范:docs
|
|
2
|
-
// 宿主组合语义:docs/feature/reports/architecture.md「
|
|
1
|
+
// niceeval show —— 终端宿主(行为规范:docs/feature/reports/show.md 与分篇;
|
|
2
|
+
// 宿主组合语义:docs/feature/reports/architecture.md「Scope 是计算入口」)。
|
|
3
3
|
//
|
|
4
4
|
// 位置参数 = eval id 前缀,或 `@<locator>`(精确指名单个 attempt,见 results/locator.ts):
|
|
5
|
-
// 裸跑 / 多 eval 前缀
|
|
5
|
+
// 裸跑 / 多 eval 前缀 内建报告(niceeval/report/built-in 默认导出)的 text 面(单 eval 前缀仍进入详情)
|
|
6
6
|
// 恰好一个 eval 单 eval 详情(attempt / 断言明细,宿主本体)
|
|
7
7
|
// @<locator> 精确 attempt:无证据 flag → 紧凑全景;带 flag → 对应证据切面
|
|
8
|
-
// --
|
|
9
|
-
// --history
|
|
10
|
-
// --report <文件> 整槽换成用户报告;位置前缀 / --
|
|
11
|
-
// --
|
|
8
|
+
// --source / --execution / --diff[=路径] 证据切面(宿主本体):出现即走证据室,不渲染报告槽
|
|
9
|
+
// --history 执行时间轴(逐 experimentId + evalId 分节),与 --report 互斥
|
|
10
|
+
// --report <文件> 整槽换成用户报告;位置前缀 / --results / --experiment 先收窄 Scope 再注入
|
|
11
|
+
// --page <id> 多页报告选页;未命中列出可用页 id 按用法错误退出
|
|
12
|
+
// --results <目录> 结果根换成该目录;--experiment 让 Scope 只留该实验
|
|
12
13
|
//
|
|
13
|
-
// 数据全部走 niceeval/results 的读取面(openResults + 合成
|
|
14
|
-
//
|
|
14
|
+
// 数据全部走 niceeval/results 的读取面(openResults + 合成 Scope + loadAttemptEvidence),
|
|
15
|
+
// 不自己爬目录;证据可用性只由 loadAttemptEvidence 在单 Attempt 页面计算。
|
|
15
16
|
|
|
16
17
|
import { existsSync } from "node:fs";
|
|
17
18
|
import { join, resolve } from "node:path";
|
|
@@ -23,33 +24,38 @@ import {
|
|
|
23
24
|
LocatorNotFoundError,
|
|
24
25
|
MalformedLocatorError,
|
|
25
26
|
} from "../results/index.ts";
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
// when niceeval is linked in — see tsconfig.report-build.json); show pulls all of it from
|
|
33
|
-
// there, not just the .tsx-touching pieces.
|
|
34
|
-
import { renderReportToText } from "../../dist/report/report.js";
|
|
35
|
-
import { ExperimentComparison } from "../../dist/report/built-ins/index.js";
|
|
36
|
-
import { ReportLoadError, loadReportFile } from "../../dist/report/load.js";
|
|
27
|
+
// ReportLoadError must come from the SAME module instance the report runtime is built
|
|
28
|
+
// against — `instanceof` is keyed by declaration site, so a raw src copy and the compiled
|
|
29
|
+
// dist copy of "the same" class are two different types. The package-owned report runtime
|
|
30
|
+
// ships as precompiled ESM (dist/report/**, built by `pnpm run build:report`); all report
|
|
31
|
+
// loading/rendering goes through ./report-host.ts (the single contact surface with it).
|
|
32
|
+
import { ReportLoadError } from "../../dist/report/load.js";
|
|
37
33
|
import { detectLocale, t } from "../i18n/index.ts";
|
|
38
34
|
import { foldEvalVerdict } from "../shared/verdict.ts";
|
|
39
35
|
import { selectCurrentResults, filterExperiments } from "../results/select.ts";
|
|
40
|
-
import {
|
|
36
|
+
import { evalPrefixPredicate } from "../shared/aggregate.ts";
|
|
37
|
+
import { attemptHistory } from "./compose.ts";
|
|
38
|
+
import {
|
|
39
|
+
HostReportError,
|
|
40
|
+
loadHostReport,
|
|
41
|
+
localizeText,
|
|
42
|
+
reportMetaFor,
|
|
43
|
+
renderHostPageText,
|
|
44
|
+
resolveReportTitle,
|
|
45
|
+
type HostCommandContext,
|
|
46
|
+
} from "./report-host.ts";
|
|
41
47
|
import {
|
|
42
48
|
attemptArtifactsPath,
|
|
43
49
|
attemptEvidenceHeader,
|
|
50
|
+
attemptHistoryText,
|
|
44
51
|
attemptIndexLine,
|
|
45
52
|
attemptOverviewText,
|
|
46
53
|
attemptsOfEval,
|
|
47
54
|
diffText,
|
|
48
55
|
evalDetailText,
|
|
49
|
-
evalHistoryText,
|
|
50
56
|
evalSourceText,
|
|
51
57
|
executionText,
|
|
52
|
-
|
|
58
|
+
pageIndexText,
|
|
53
59
|
timingText,
|
|
54
60
|
pickDetailAttempt,
|
|
55
61
|
skippedRunsText,
|
|
@@ -57,8 +63,8 @@ import {
|
|
|
57
63
|
} from "./render.ts";
|
|
58
64
|
|
|
59
65
|
export interface ShowFlags {
|
|
60
|
-
/**
|
|
61
|
-
|
|
66
|
+
/** --source:该 attempt 运行时保存的 Eval 源码,断言标回源码行(证据切面)。 */
|
|
67
|
+
source?: boolean;
|
|
62
68
|
/** 该 attempt 的标准执行事件流 + OTel enrichment(证据切面)。 */
|
|
63
69
|
execution?: boolean;
|
|
64
70
|
/** --timing:默认有界诊断投影;full 逐节点展开。boolean 仅供库调用兼容,等价 summary。 */
|
|
@@ -69,8 +75,11 @@ export interface ShowFlags {
|
|
|
69
75
|
diffPath?: string;
|
|
70
76
|
history?: boolean;
|
|
71
77
|
experiment?: string;
|
|
72
|
-
|
|
78
|
+
/** --results:结果根目录(某次快照根或 `copySnapshots` 产物)。 */
|
|
79
|
+
results?: string;
|
|
73
80
|
report?: string;
|
|
81
|
+
/** --page:多页报告选页;未命中按用法错误退出并列出可用页 id。 */
|
|
82
|
+
page?: string;
|
|
74
83
|
}
|
|
75
84
|
|
|
76
85
|
/** 注入 IO 供测试;默认写 stdout/stderr、宽度取终端列数。 */
|
|
@@ -89,8 +98,8 @@ function clampWidth(columns: number | undefined): number {
|
|
|
89
98
|
return Math.max(40, Math.min(columns as number, 160));
|
|
90
99
|
}
|
|
91
100
|
|
|
92
|
-
// --report
|
|
93
|
-
export {
|
|
101
|
+
// --report 的装载与规范化住在 ./report-host.ts(两个宿主共用的唯一联系面)。
|
|
102
|
+
export { loadHostReport, normalizeHostReport, resolveReportTitle, localizeText } from "./report-host.ts";
|
|
94
103
|
|
|
95
104
|
export async function runShow(
|
|
96
105
|
cwd: string,
|
|
@@ -109,7 +118,7 @@ export async function runShow(
|
|
|
109
118
|
});
|
|
110
119
|
return 0;
|
|
111
120
|
} catch (e) {
|
|
112
|
-
if (e instanceof ShowError || e instanceof ReportLoadError) {
|
|
121
|
+
if (e instanceof ShowError || e instanceof ReportLoadError || e instanceof HostReportError) {
|
|
113
122
|
err(e.message.endsWith("\n") ? e.message : `${e.message}\n`);
|
|
114
123
|
return 1;
|
|
115
124
|
}
|
|
@@ -124,7 +133,7 @@ async function show(
|
|
|
124
133
|
io: { out: (s: string) => void; err: (s: string) => void; width: number; now: number },
|
|
125
134
|
): Promise<void> {
|
|
126
135
|
const evidence =
|
|
127
|
-
flags.
|
|
136
|
+
flags.source === true ||
|
|
128
137
|
flags.execution === true ||
|
|
129
138
|
(flags.timing !== undefined && flags.timing !== false) ||
|
|
130
139
|
flags.diff === true ||
|
|
@@ -135,8 +144,15 @@ async function show(
|
|
|
135
144
|
throw new ShowError(t("cli.show.historyReportConflict"));
|
|
136
145
|
}
|
|
137
146
|
|
|
138
|
-
|
|
139
|
-
if (flags.
|
|
147
|
+
// --page 只在报告槽里有意义:证据切面 / 时间轴与它组合是用法矛盾,先于任何 IO 报出来。
|
|
148
|
+
if (flags.page !== undefined && (evidence || flags.history)) {
|
|
149
|
+
throw new ShowError(
|
|
150
|
+
`--page selects a report page and cannot be combined with ${flags.history ? "--history" : "evidence flags"}.\n`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const root = flags.results !== undefined ? resolve(cwd, flags.results) : join(cwd, ".niceeval");
|
|
155
|
+
if (flags.results !== undefined && !existsSync(root)) {
|
|
140
156
|
throw new ShowError(t("cli.show.runDirMissing", { dir: root }));
|
|
141
157
|
}
|
|
142
158
|
|
|
@@ -150,7 +166,7 @@ async function show(
|
|
|
150
166
|
// (`@` 打头对 eval id 天然无歧义,见 locator.ts),必须在下面的前缀匹配逻辑之前分流掉,
|
|
151
167
|
// 不然 "@1x7f3q" 会被当成一个谁都匹配不到的 eval id 前缀,报「no eval match」这种文不对题的
|
|
152
168
|
// 错误。这一步只解析并渲染出「当前 show 对单个已解析 attempt 能渲染的东西」(单 eval 详情 /
|
|
153
|
-
// 三个证据切面)——真正的 `--
|
|
169
|
+
// 三个证据切面)——真正的 `--source`/`--execution`/`--diff` 统一 attempt 全景是后续阶段。
|
|
154
170
|
const locatorArg = patterns.find((p) => p.startsWith(ATTEMPT_LOCATOR_PREFIX));
|
|
155
171
|
if (locatorArg !== undefined) {
|
|
156
172
|
if (patterns.length !== 1) {
|
|
@@ -171,7 +187,7 @@ async function show(
|
|
|
171
187
|
const artifactPath = attemptArtifactsPath(attempt, cwd);
|
|
172
188
|
if (evidence) {
|
|
173
189
|
const blocks: string[] = [];
|
|
174
|
-
if (flags.
|
|
190
|
+
if (flags.source) blocks.push(evalSourceText(attemptEvidence, { header, artifactPath, width: io.width }));
|
|
175
191
|
if (flags.execution) blocks.push(executionText(attemptEvidence, { header, artifactPath, width: io.width }));
|
|
176
192
|
if (flags.timing) blocks.push(timingText(attemptEvidence, {
|
|
177
193
|
header,
|
|
@@ -240,7 +256,7 @@ async function show(
|
|
|
240
256
|
const header = attemptEvidenceHeader(attemptEvidence);
|
|
241
257
|
const artifactPath = attemptArtifactsPath(picked, cwd);
|
|
242
258
|
const blocks: string[] = [];
|
|
243
|
-
if (flags.
|
|
259
|
+
if (flags.source) blocks.push(evalSourceText(attemptEvidence, { header, artifactPath, width: io.width }));
|
|
244
260
|
if (flags.execution) blocks.push(executionText(attemptEvidence, { header, artifactPath, width: io.width }));
|
|
245
261
|
if (flags.timing) blocks.push(timingText(attemptEvidence, {
|
|
246
262
|
header,
|
|
@@ -255,30 +271,30 @@ async function show(
|
|
|
255
271
|
return;
|
|
256
272
|
}
|
|
257
273
|
|
|
258
|
-
// --history
|
|
274
|
+
// --history:执行时间轴(docs/feature/reports/show.md「--history:一个 eval 的执行时间轴」)。
|
|
275
|
+
// 对 Scope 中匹配的每个 experimentId + evalId 分节,逐 attempt 而非逐快照;时间轴只列
|
|
276
|
+
// 真实执行 —— resume 携带的复印件按 attempt 身份键去重后不占行。
|
|
259
277
|
if (flags.history) {
|
|
260
278
|
const experiments = filterExperiments(results.experiments, flags.experiment);
|
|
279
|
+
// eval 位置参数与 Scope 选择用同一个前缀谓词(单点在 shared/aggregate.ts),不另立口径。
|
|
280
|
+
const matchesPattern = patterns.length > 0 ? evalPrefixPredicate(patterns) : () => true;
|
|
261
281
|
const blocks: string[] = [];
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const rows = evalHistory(exp, evalId);
|
|
269
|
-
if (rows.length === 0) continue;
|
|
270
|
-
blocks.push(evalHistoryText({ experimentId: exp.id, ...(multi ? { evalId } : {}), rows }));
|
|
271
|
-
}
|
|
282
|
+
for (const exp of experiments) {
|
|
283
|
+
const evalIds = [...exp.evalIds].filter(matchesPattern).sort();
|
|
284
|
+
for (const evalId of evalIds) {
|
|
285
|
+
const rows = attemptHistory(exp, evalId);
|
|
286
|
+
if (rows.length === 0) continue;
|
|
287
|
+
blocks.push(attemptHistoryText({ experimentId: exp.id, evalId, rows }));
|
|
272
288
|
}
|
|
273
289
|
}
|
|
274
290
|
io.out(blocks.join("\n\n") + "\n");
|
|
275
291
|
return;
|
|
276
292
|
}
|
|
277
293
|
|
|
278
|
-
// 单 eval 详情(宿主本体);--report 在场时报告槽优先,前缀只用来收窄
|
|
294
|
+
// 单 eval 详情(宿主本体);--report / --page 在场时报告槽优先,前缀只用来收窄 Scope。
|
|
279
295
|
// 挑哪个 attempt 展开明细不再收数字 --attempt——pickDetailAttempt 的默认启发式
|
|
280
296
|
// (最新一次失败,没有失败挑最新一次)是唯一路径;精确选某一次走 `@<locator>`。
|
|
281
|
-
if (flags.report === undefined && patterns.length > 0 && matchedEvalIds.length === 1) {
|
|
297
|
+
if (flags.report === undefined && flags.page === undefined && patterns.length > 0 && matchedEvalIds.length === 1) {
|
|
282
298
|
const evalId = matchedEvalIds[0];
|
|
283
299
|
const attempts = attemptsOfEval(selection.snapshots, evalId);
|
|
284
300
|
const detail = pickDetailAttempt(attempts);
|
|
@@ -295,13 +311,48 @@ async function show(
|
|
|
295
311
|
return;
|
|
296
312
|
}
|
|
297
313
|
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
314
|
+
// 报告槽:裸 show 装载 `niceeval/report/built-in` 的默认导出,--report 整槽替换——同一条
|
|
315
|
+
// 「装载 → 规范化(外壳 + 非空页列表)→ 逐页渲染」管线(docs/feature/reports/library/shell.md)。
|
|
316
|
+
// locale = CLI 界面语言(NICEEVAL_LANG / LC_* / LANG 检测):报告 chrome 文案跟随终端语言。
|
|
317
|
+
const report = await loadHostReport(cwd, flags.report);
|
|
318
|
+
const locale = detectLocale();
|
|
319
|
+
const commandContext: HostCommandContext = {
|
|
320
|
+
patterns,
|
|
321
|
+
...(flags.results !== undefined ? { results: flags.results } : {}),
|
|
322
|
+
...(flags.report !== undefined ? { report: flags.report } : {}),
|
|
323
|
+
...(flags.experiment !== undefined ? { experiment: flags.experiment } : {}),
|
|
324
|
+
};
|
|
325
|
+
const sourceLabel = flags.report ?? "the built-in report";
|
|
326
|
+
|
|
327
|
+
let page = report.pages[0];
|
|
328
|
+
if (flags.page !== undefined) {
|
|
329
|
+
const hit = report.pages.find((p) => p.id === flags.page);
|
|
330
|
+
if (!hit) {
|
|
331
|
+
// 用法错误:列出可用页 id(docs/feature/reports/show/reports.md Case 1/2 的报错样例)。
|
|
332
|
+
throw new ShowError(
|
|
333
|
+
`error: page "${flags.page}" not found in ${sourceLabel}. Available pages: ${report.pages.map((p) => p.id).join(", ")}\n`,
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
page = hit;
|
|
337
|
+
} else if (report.pages.length > 1) {
|
|
338
|
+
// 多页未选页:只输出页索引与可复制的单页命令,不倾倒页内容(与可比组索引同一模式)。
|
|
339
|
+
// 标题行走标题回退链(终点是内置文案「Eval 运行结果 / Eval Results」,恒有值)。
|
|
340
|
+
const title = localizeText(resolveReportTitle(report.title, selection.snapshots), locale) ?? "Eval Results";
|
|
341
|
+
io.out(pageIndexText({ report, title, command: commandContext, locale }) + "\n");
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// attemptCommand 留给渲染管线的默认值:AttemptLocator 已经是可直接 `niceeval show @<locator>`
|
|
346
|
+
// 的真实 CLI 语法,不需要再反查 eval id 拼一条近似命令。
|
|
347
|
+
const meta = reportMetaFor(report, page.id, selection.snapshots);
|
|
348
|
+
const text = await renderHostPageText(
|
|
349
|
+
page,
|
|
350
|
+
{ scope: selection, results, report: meta },
|
|
351
|
+
{
|
|
352
|
+
width: io.width,
|
|
353
|
+
locale,
|
|
354
|
+
commandContext: { ...commandContext, ...(flags.page !== undefined ? { page: flags.page } : {}) },
|
|
355
|
+
},
|
|
356
|
+
);
|
|
306
357
|
io.out(text + "\n");
|
|
307
358
|
}
|
package/src/show/render.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// show 的文本渲染:单 eval 详情、--history 时间轴、三个证据切面(transcript / trace / diff)。
|
|
2
|
-
// 输出形态照 docs-site/zh/
|
|
2
|
+
// 输出形态照 docs-site/zh/how-to/viewing-results.mdx 的示例块;长内容一律截断,
|
|
3
3
|
// 但截断永远如实标注剩余数量和原始 artifact 路径 —— 输出对上下文窗口友好,事实源留在盘上。
|
|
4
4
|
// 全部纯函数(时间经 now 显式传入),证据数据由调用方 await 好了递进来。
|
|
5
5
|
|
|
@@ -15,7 +15,8 @@ import { summaryText } from "../scoring/display.ts";
|
|
|
15
15
|
import { attemptCostUSD } from "../report/metrics.ts";
|
|
16
16
|
import { formatDurationMs, formatMetricValue, formatPlainNumber, formatUSD } from "../report/format.ts";
|
|
17
17
|
import { indentBlock, padDisplay, renderAlignedRows, wrapDisplay } from "../report/text/layout.ts";
|
|
18
|
-
import type {
|
|
18
|
+
import type { AttemptHistoryRow } from "./compose.ts";
|
|
19
|
+
import { localizeText, showCommand, type HostCommandContext, type HostReport } from "./report-host.ts";
|
|
19
20
|
|
|
20
21
|
const MISSING = "—";
|
|
21
22
|
|
|
@@ -72,8 +73,8 @@ export function attemptArtifactsPath(attempt: AttemptHandle, cwd: string): strin
|
|
|
72
73
|
|
|
73
74
|
/**
|
|
74
75
|
* 裸 `show` 零可读结果时,skipped 目录的展示文案。niceeval 自己写的、schemaVersion 不兼容的
|
|
75
|
-
* 落盘按 producer 版本分组,一条 `npx niceeval@<version> show --
|
|
76
|
-
* 快照——show 没有 view 的单快照直读模式,`--
|
|
76
|
+
* 落盘按 producer 版本分组,一条 `npx niceeval@<version> show --results <结果根>` 覆盖同版本全部
|
|
77
|
+
* 快照——show 没有 view 的单快照直读模式,`--results` 认的是结果根(其下可以有多个 experiment),
|
|
77
78
|
* 不是单个快照目录,所以不对每个目录重复拼一条各自的命令(那条命令用同一个 root 跑起来
|
|
78
79
|
* 结果完全一样,重复只会刷屏)。其余(第三方 harness、版本信息缺失、malformed、incomplete)
|
|
79
80
|
* 没有可执行的统一建议,原样逐条列出。
|
|
@@ -86,7 +87,7 @@ export function skippedRunsText(skipped: readonly SkippedDir[], root: string, cw
|
|
|
86
87
|
const count = g.dirs.length;
|
|
87
88
|
const version = g.producer.version;
|
|
88
89
|
const schema = g.schemaVersion !== undefined ? ` (schemaVersion ${g.schemaVersion})` : "";
|
|
89
|
-
const cmd = `npx niceeval@${version} show --
|
|
90
|
+
const cmd = `npx niceeval@${version} show --results ${rootDisplay}`;
|
|
90
91
|
lines.push(
|
|
91
92
|
` ${count} snapshot${count === 1 ? "" : "s"} written by niceeval ${version}${schema} — run \`${cmd}\` to view`,
|
|
92
93
|
);
|
|
@@ -374,8 +375,8 @@ export function evalDetailText(opts: EvalDetailOptions): string {
|
|
|
374
375
|
if (detail.locator) tail.push(`attempt locator: ${detail.locator}`);
|
|
375
376
|
tail.push(
|
|
376
377
|
detail.locator
|
|
377
|
-
? `next: niceeval show ${detail.locator} [--
|
|
378
|
-
: `next: niceeval show ${evalId} [--
|
|
378
|
+
? `next: niceeval show ${detail.locator} [--source|--execution|--diff]`
|
|
379
|
+
: `next: niceeval show ${evalId} [--source|--execution|--diff]`,
|
|
379
380
|
);
|
|
380
381
|
blocks.push(tail.join("\n"));
|
|
381
382
|
}
|
|
@@ -385,43 +386,58 @@ export function evalDetailText(opts: EvalDetailOptions): string {
|
|
|
385
386
|
|
|
386
387
|
// ───────────────────────── --history ─────────────────────────
|
|
387
388
|
|
|
388
|
-
/**
|
|
389
|
-
|
|
389
|
+
/**
|
|
390
|
+
* 一个 experimentId + evalId 的执行时间轴分节(docs/feature/reports/show.md「--history」):
|
|
391
|
+
* 节头 `<evalId> · <experimentId> · N attempts · passed x/N`,节内每行
|
|
392
|
+
* 时间 / verdict / 单行摘要 / 耗时 / 成本 / locator,startedAt 升序(compose 已排好)。
|
|
393
|
+
*/
|
|
394
|
+
export function attemptHistoryText(opts: {
|
|
390
395
|
experimentId: string;
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
rows: EvalHistoryRow[];
|
|
396
|
+
evalId: string;
|
|
397
|
+
rows: AttemptHistoryRow[];
|
|
394
398
|
}): string {
|
|
395
399
|
const { experimentId, evalId, rows } = opts;
|
|
396
400
|
const passed = rows.filter((r) => r.verdict === "passed").length;
|
|
397
401
|
const head = [
|
|
398
|
-
|
|
402
|
+
evalId,
|
|
399
403
|
experimentId,
|
|
400
|
-
|
|
404
|
+
attemptsLabel(rows.length),
|
|
401
405
|
`passed ${passed}/${rows.length}`,
|
|
402
406
|
].join(" · ");
|
|
403
407
|
if (rows.length === 0) return head;
|
|
404
408
|
const table = renderAlignedRows(
|
|
405
409
|
rows.map((r) => [
|
|
406
|
-
timelineStamp(r.startedAt),
|
|
410
|
+
r.startedAt !== undefined ? timelineStamp(r.startedAt) : MISSING,
|
|
407
411
|
`${verdictMark(r.verdict)} ${r.verdict}`,
|
|
408
|
-
|
|
412
|
+
r.summary ?? MISSING,
|
|
413
|
+
formatDurationMs(r.durationMs),
|
|
409
414
|
r.costUSD === null ? MISSING : formatUSD(r.costUSD),
|
|
410
|
-
r.
|
|
415
|
+
r.locator ?? MISSING,
|
|
411
416
|
]),
|
|
412
417
|
);
|
|
413
418
|
return `${head}\n\n${indentBlock(table, " ")}`;
|
|
414
419
|
}
|
|
415
420
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
421
|
+
// ───────────────────────── --report 页索引 ─────────────────────────
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* 多页报告的页索引(docs/feature/reports/show/reports.md Case 2):标题行 + 每页一行
|
|
425
|
+
* (id / 本 locale 页名 / 可复制的 `--page` 命令)。索引命令携带完整上下文
|
|
426
|
+
* (--results / --report / 位置参数),复制即可精确复现下一层视图。
|
|
427
|
+
*/
|
|
428
|
+
export function pageIndexText(opts: {
|
|
429
|
+
report: HostReport;
|
|
430
|
+
title: string;
|
|
431
|
+
command: HostCommandContext;
|
|
432
|
+
locale: string;
|
|
433
|
+
}): string {
|
|
434
|
+
const { report, title, command, locale } = opts;
|
|
435
|
+
const head = `${title} · ${locale === "zh-CN" ? `${report.pages.length} 页` : `${report.pages.length} pages`}`;
|
|
420
436
|
const table = renderAlignedRows(
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
437
|
+
report.pages.map((page) => [
|
|
438
|
+
page.id,
|
|
439
|
+
localizeText(page.title, locale) ?? page.id,
|
|
440
|
+
showCommand({ ...command, page: page.id }),
|
|
425
441
|
]),
|
|
426
442
|
);
|
|
427
443
|
return `${head}\n\n${indentBlock(table, " ")}`;
|
|
@@ -603,7 +619,7 @@ function execBody(
|
|
|
603
619
|
/**
|
|
604
620
|
* action / subagent 节点渲染成两行(call + result)——节点模型把 call+result 合并成一个
|
|
605
621
|
* ExecutionNode(按 callId),但分两行读更符合「先看调用、再看结果」的阅读顺序,
|
|
606
|
-
* 与 docs-site/zh/
|
|
622
|
+
* 与 docs-site/zh/how-to/agent-feedback-loop.mdx 的示例一致。
|
|
607
623
|
*/
|
|
608
624
|
function executionNodeLines(node: ExecutionNode, originMs: number, timingAvailable: boolean, width: number): string[] {
|
|
609
625
|
const time = node.kind !== "telemetry" && node.span ? relSeconds(node.span.startMs, originMs) : undefined;
|
|
@@ -889,7 +905,7 @@ export function attemptOverviewText(
|
|
|
889
905
|
const tail: string[] = [];
|
|
890
906
|
if (artifactPath) tail.push(`artifacts: ${artifactPath}/`);
|
|
891
907
|
const available = [
|
|
892
|
-
evidence.capabilities.
|
|
908
|
+
evidence.capabilities.source ? `niceeval show ${evidence.locator} --source` : undefined,
|
|
893
909
|
evidence.capabilities.execution ? `niceeval show ${evidence.locator} --execution` : undefined,
|
|
894
910
|
evidence.capabilities.timing ? `niceeval show ${evidence.locator} --timing` : undefined,
|
|
895
911
|
evidence.capabilities.diff ? `niceeval show ${evidence.locator} --diff` : undefined,
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// cases: docs/engineering/unit-tests/reports/cases.md
|
|
2
|
+
// 「外壳、页面与 Tabs」——宿主装载规范化、页索引命令上下文与标题回退链
|
|
3
|
+
// (契约:docs/feature/reports/library/shell.md)。
|
|
4
|
+
// 页内树的 resolve / render 归报告库测试;这里只测宿主侧的规范化与选择逻辑(纯函数)。
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from "vitest";
|
|
7
|
+
import {
|
|
8
|
+
BUILT_IN_PAGE_TITLE,
|
|
9
|
+
BUILT_IN_REPORT_TITLE,
|
|
10
|
+
HostReportError,
|
|
11
|
+
localizeText,
|
|
12
|
+
localizedTextEquals,
|
|
13
|
+
normalizeHostReport,
|
|
14
|
+
resolveReportTitle,
|
|
15
|
+
showCommand,
|
|
16
|
+
} from "./report-host.ts";
|
|
17
|
+
import { pageIndexText } from "./render.ts";
|
|
18
|
+
|
|
19
|
+
const tree = { kind: "node" }; // 页 content 对宿主是不透明值,规范化不解析树
|
|
20
|
+
|
|
21
|
+
describe("装载规范化:外壳 + 非空页列表", () => {
|
|
22
|
+
it("content 缩写展开为唯一页 id `report`,页名是内置页名「报告 / Report」", () => {
|
|
23
|
+
const report = normalizeHostReport({ kind: "report", content: tree }, "reports/frontier.tsx");
|
|
24
|
+
expect(report.pages).toHaveLength(1);
|
|
25
|
+
expect(report.pages[0]).toMatchObject({ id: "report", title: BUILT_IN_PAGE_TITLE, content: tree });
|
|
26
|
+
expect(report.links).toEqual([]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("pages 形态按声明序保留页列表与外壳字段", () => {
|
|
30
|
+
const report = normalizeHostReport(
|
|
31
|
+
{
|
|
32
|
+
kind: "report",
|
|
33
|
+
title: { en: "Memory Evals", "zh-CN": "记忆能力评测" },
|
|
34
|
+
links: [{ label: "GitHub", href: "https://example.com" }],
|
|
35
|
+
footer: "Published nightly.",
|
|
36
|
+
pages: [
|
|
37
|
+
{ id: "overview", title: { en: "Overview", "zh-CN": "总览" }, content: tree },
|
|
38
|
+
{ id: "exam", title: { en: "Exam", "zh-CN": "成绩单" }, content: tree },
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
"reports/site.tsx",
|
|
42
|
+
);
|
|
43
|
+
expect(report.pages.map((p) => p.id)).toEqual(["overview", "exam"]);
|
|
44
|
+
expect(report.title).toEqual({ en: "Memory Evals", "zh-CN": "记忆能力评测" });
|
|
45
|
+
expect(report.footer).toBe("Published nightly.");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("content 与 pages 恰好声明一个:同给 / 同缺都报错,文案给出 <ExperimentComparison /> 下一步", () => {
|
|
49
|
+
for (const bad of [
|
|
50
|
+
{ kind: "report", content: tree, pages: [{ id: "a", title: "A", content: tree }] },
|
|
51
|
+
{ kind: "report", title: "T" },
|
|
52
|
+
]) {
|
|
53
|
+
expect(() => normalizeHostReport(bad, "reports/site.tsx")).toThrow(HostReportError);
|
|
54
|
+
expect(() => normalizeHostReport(bad, "reports/site.tsx")).toThrow(/<ExperimentComparison \/>/);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("空 pages 列表 / 重复 page id / 非法 page id 在装载期报错", () => {
|
|
59
|
+
expect(() => normalizeHostReport({ kind: "report", pages: [] }, "r.tsx")).toThrow(/non-empty/);
|
|
60
|
+
expect(() =>
|
|
61
|
+
normalizeHostReport(
|
|
62
|
+
{ kind: "report", pages: [{ id: "exam", title: "A", content: tree }, { id: "exam", title: "B", content: tree }] },
|
|
63
|
+
"r.tsx",
|
|
64
|
+
),
|
|
65
|
+
).toThrow(/duplicate page id "exam"/);
|
|
66
|
+
for (const id of ["Exam", "a/b"]) {
|
|
67
|
+
expect(() =>
|
|
68
|
+
normalizeHostReport({ kind: "report", pages: [{ id, title: "A", content: tree }] }, "r.tsx"),
|
|
69
|
+
).toThrow(/invalid/);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("默认导出不是 defineReport 产物:完整用户反馈", () => {
|
|
74
|
+
expect(() => normalizeHostReport({ some: "object" }, "reports/bad.tsx")).toThrow(
|
|
75
|
+
/does not default-export a report/,
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("ReportLink.icon 是 { svg: string }:合法形状原样透传;无类型 JS 传其它形状装载报错", () => {
|
|
80
|
+
const svg = '<svg viewBox="0 0 16 16"><path d="M0 0h16v16z"/></svg>';
|
|
81
|
+
const report = normalizeHostReport(
|
|
82
|
+
{ kind: "report", content: tree, links: [{ label: "GitHub", href: "https://example.com", icon: { svg } }] },
|
|
83
|
+
"reports/site.tsx",
|
|
84
|
+
);
|
|
85
|
+
expect(report.links[0]!.icon).toEqual({ svg });
|
|
86
|
+
|
|
87
|
+
// ReactNode / 组件 / 裸字符串都不是 { svg: string },装载期以完整用户反馈拒绝。
|
|
88
|
+
const reactNode = { $$typeof: Symbol.for("react.transitional.element"), type: "svg", props: {} };
|
|
89
|
+
for (const icon of [reactNode, "<svg/>", { svg: 42 }, { svg: "" }]) {
|
|
90
|
+
expect(() =>
|
|
91
|
+
normalizeHostReport(
|
|
92
|
+
{ kind: "report", content: tree, links: [{ label: "GitHub", href: "https://example.com", icon }] },
|
|
93
|
+
"reports/site.tsx",
|
|
94
|
+
),
|
|
95
|
+
).toThrow(/icon" must be \{ svg: string \}/);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("旧 build 函数形态(集成前桥接)恒为单页 report", () => {
|
|
100
|
+
const legacy = { build: () => tree };
|
|
101
|
+
const report = normalizeHostReport(legacy, "the built-in report");
|
|
102
|
+
expect(report.pages.map((p) => p.id)).toEqual(["report"]);
|
|
103
|
+
expect(report.pages[0]!.content).toBe(legacy);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("标题回退链:def.title → 唯一且相同的快照 name → 内置文案「Eval 运行结果 / Eval Results」", () => {
|
|
108
|
+
it("def.title 优先", () => {
|
|
109
|
+
expect(resolveReportTitle({ en: "T" }, [{ name: "S" }])).toEqual({ en: "T" });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("无 def.title 时取 Scope 中唯一且相同(深相等,键顺序无关)的非空快照 name", () => {
|
|
113
|
+
expect(resolveReportTitle(undefined, [{ name: { en: "S", "zh-CN": "斯" } }, { name: { "zh-CN": "斯", en: "S" } }]))
|
|
114
|
+
.toEqual({ en: "S", "zh-CN": "斯" });
|
|
115
|
+
expect(resolveReportTitle(undefined, [{}, { name: "Only" }])).toBe("Only");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("多个不同 name(en 相同、zh-CN 不同也算不同)不随机挑,落内置文案;全无 name 亦然", () => {
|
|
119
|
+
expect(
|
|
120
|
+
resolveReportTitle(undefined, [{ name: { en: "S", "zh-CN": "甲" } }, { name: { en: "S", "zh-CN": "乙" } }]),
|
|
121
|
+
).toEqual(BUILT_IN_REPORT_TITLE);
|
|
122
|
+
expect(resolveReportTitle(undefined, [{}, {}])).toEqual(BUILT_IN_REPORT_TITLE);
|
|
123
|
+
expect(resolveReportTitle("", [])).toEqual(BUILT_IN_REPORT_TITLE); // 空串标题不算声明
|
|
124
|
+
// 链终点是内置文案(shell.md),不是产品名——品牌位固定 NiceEval,不吃标题。
|
|
125
|
+
expect(BUILT_IN_REPORT_TITLE).toEqual({ en: "Eval Results", "zh-CN": "Eval 运行结果" });
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("LocalizedText 深相等按字段值,不看键顺序", () => {
|
|
129
|
+
expect(localizedTextEquals({ a: "1", b: "2" }, { b: "2", a: "1" })).toBe(true);
|
|
130
|
+
expect(localizedTextEquals({ a: "1" }, { a: "1", b: "2" })).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("LocalizedText 回退:locale → en → 键字典序第一个非空值", () => {
|
|
135
|
+
it("三级回退各自命中", () => {
|
|
136
|
+
expect(localizeText({ "zh-CN": "中", en: "E" }, "zh-CN")).toBe("中");
|
|
137
|
+
expect(localizeText({ "zh-CN": "中", en: "E" }, "fr")).toBe("E");
|
|
138
|
+
expect(localizeText({ "zh-TW": "繁", ja: "日" }, "en")).toBe("日"); // ja < zh-TW 字典序
|
|
139
|
+
expect(localizeText("plain", "en")).toBe("plain");
|
|
140
|
+
expect(localizeText({}, "en")).toBeUndefined();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("页索引与索引命令上下文", () => {
|
|
145
|
+
const report = normalizeHostReport(
|
|
146
|
+
{
|
|
147
|
+
kind: "report",
|
|
148
|
+
title: { en: "Memory Evals", "zh-CN": "记忆能力评测" },
|
|
149
|
+
links: [{ label: "GitHub", href: "https://example.com", icon: { svg: "<svg data-mark></svg>" } }],
|
|
150
|
+
pages: [
|
|
151
|
+
{ id: "overview", title: { en: "Overview", "zh-CN": "总览" }, content: tree },
|
|
152
|
+
{ id: "exam", title: { en: "Exam", "zh-CN": "成绩单" }, content: tree },
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
"reports/site.tsx",
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
it("索引命令保留当前 --results / --report 与位置参数,复制即可复现下一层视图", () => {
|
|
159
|
+
const text = pageIndexText({
|
|
160
|
+
report,
|
|
161
|
+
title: "记忆能力评测",
|
|
162
|
+
command: { patterns: [], results: "tmp/published-results", report: "reports/site.tsx" },
|
|
163
|
+
locale: "zh-CN",
|
|
164
|
+
});
|
|
165
|
+
expect(text).toContain("记忆能力评测 · 2 页");
|
|
166
|
+
expect(text).toContain("niceeval show --results tmp/published-results --report reports/site.tsx --page overview");
|
|
167
|
+
expect(text).toContain("niceeval show --results tmp/published-results --report reports/site.tsx --page exam");
|
|
168
|
+
expect(text).toContain("总览");
|
|
169
|
+
expect(text).toContain("成绩单");
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("show 不消费 links:页索引不含 icon svg 与 href(icon 是 web 面属性)", () => {
|
|
173
|
+
const text = pageIndexText({
|
|
174
|
+
report,
|
|
175
|
+
title: "Memory Evals",
|
|
176
|
+
command: { patterns: [] },
|
|
177
|
+
locale: "en",
|
|
178
|
+
});
|
|
179
|
+
expect(text).not.toContain("<svg");
|
|
180
|
+
expect(text).not.toContain("https://example.com");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("showCommand 按序携带位置参数与 --experiment / --results / --report / --page", () => {
|
|
184
|
+
expect(
|
|
185
|
+
showCommand({ patterns: ["memory/swelancer"], experiment: "dev-e2b", report: "reports/site.tsx", page: "exam" }),
|
|
186
|
+
).toBe("niceeval show memory/swelancer --experiment dev-e2b --report reports/site.tsx --page exam");
|
|
187
|
+
});
|
|
188
|
+
});
|