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