niceeval 0.5.4 → 0.6.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 +55 -0
- package/dist/agents/types.d.ts +321 -0
- package/dist/context/types.d.ts +335 -0
- package/dist/i18n/core.d.ts +11 -0
- package/dist/i18n/en.d.ts +181 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/zh-CN.d.ts +183 -0
- package/dist/o11y/otlp/parse.d.ts +3 -0
- package/dist/o11y/otlp/receiver.d.ts +21 -0
- package/dist/o11y/otlp/turn-otel.d.ts +43 -0
- package/dist/o11y/types.d.ts +190 -0
- package/dist/report/aggregate.d.ts +57 -0
- package/dist/report/aggregate.js +212 -0
- package/dist/report/built-ins/experiment-comparison.d.ts +1 -0
- package/dist/report/built-ins/experiment-comparison.js +13 -0
- package/dist/report/built-ins/index.d.ts +1 -0
- package/dist/report/built-ins/index.js +2 -0
- package/dist/report/components.d.ts +180 -0
- package/dist/report/components.js +132 -0
- package/dist/report/compute.d.ts +106 -0
- package/dist/report/compute.js +600 -0
- package/dist/report/flag.d.ts +6 -0
- package/dist/report/flag.js +8 -0
- package/dist/report/format.d.ts +26 -0
- package/dist/report/format.js +109 -0
- package/dist/report/index.d.ts +19 -0
- package/dist/report/index.js +30 -0
- package/dist/report/load.d.ts +12 -0
- package/dist/report/load.js +56 -0
- package/dist/report/locale.d.ts +94 -0
- package/dist/report/locale.js +183 -0
- package/dist/report/metrics.d.ts +21 -0
- package/dist/report/metrics.js +115 -0
- package/dist/report/primitives.d.ts +64 -0
- package/dist/report/primitives.js +113 -0
- package/dist/report/react/AttemptList.d.ts +23 -0
- package/dist/report/react/AttemptList.js +19 -0
- package/dist/report/react/DeltaTable.d.ts +8 -0
- package/dist/report/react/DeltaTable.js +24 -0
- package/dist/report/react/EvalList.d.ts +10 -0
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentList.d.ts +11 -0
- package/dist/report/react/ExperimentList.js +67 -0
- package/dist/report/react/GroupSummary.d.ts +8 -0
- package/dist/report/react/GroupSummary.js +8 -0
- package/dist/report/react/MetricBars.d.ts +10 -0
- package/dist/report/react/MetricBars.js +48 -0
- package/dist/report/react/MetricLine.d.ts +9 -0
- package/dist/report/react/MetricLine.js +68 -0
- package/dist/report/react/MetricMatrix.d.ts +10 -0
- package/dist/report/react/MetricMatrix.js +23 -0
- package/dist/report/react/MetricScatter.d.ts +9 -0
- package/dist/report/react/MetricScatter.js +96 -0
- package/dist/report/react/MetricTable.d.ts +11 -0
- package/dist/report/react/MetricTable.js +26 -0
- package/dist/report/react/RunOverview.d.ts +8 -0
- package/dist/report/react/RunOverview.js +12 -0
- package/dist/report/react/Scoreboard.d.ts +8 -0
- package/dist/report/react/Scoreboard.js +26 -0
- package/dist/report/react/cell.d.ts +9 -0
- package/dist/report/react/cell.js +9 -0
- package/dist/report/react/chart-math.d.ts +14 -0
- package/dist/report/react/chart-math.js +68 -0
- package/dist/report/react/colors.d.ts +14 -0
- package/dist/report/react/colors.js +45 -0
- package/dist/report/react/fixtures.d.ts +19 -0
- package/dist/report/react/fixtures.js +398 -0
- package/dist/report/react/format.d.ts +3 -0
- package/dist/report/react/format.js +7 -0
- package/dist/report/react/index.d.ts +6 -0
- package/dist/report/react/index.js +14 -0
- package/dist/report/report.d.ts +24 -0
- package/dist/report/report.js +49 -0
- package/dist/report/text/faces.d.ts +29 -0
- package/dist/report/text/faces.js +541 -0
- package/dist/report/text/layout.d.ts +75 -0
- package/dist/report/text/layout.js +173 -0
- package/dist/report/text/plot.d.ts +37 -0
- package/dist/report/text/plot.js +81 -0
- package/dist/report/text/table.d.ts +8 -0
- package/dist/report/text/table.js +86 -0
- package/dist/report/tree.d.ts +99 -0
- package/dist/report/tree.js +228 -0
- package/dist/report/types.d.ts +408 -0
- package/dist/report/types.js +4 -0
- package/dist/report/web.d.ts +15 -0
- package/dist/report/web.js +37 -0
- package/dist/results/locator.d.ts +75 -0
- package/dist/results/locator.js +0 -0
- package/dist/results/select.d.ts +45 -0
- package/dist/results/select.js +230 -0
- package/dist/results/source-hash.d.ts +9 -0
- package/dist/results/types.d.ts +204 -0
- package/dist/runner/eval-source.d.ts +22 -0
- package/dist/runner/fingerprint.d.ts +23 -0
- package/dist/runner/types.d.ts +332 -0
- package/dist/sandbox/docker-stream.d.ts +24 -0
- package/dist/sandbox/docker.d.ts +84 -0
- package/dist/sandbox/e2b.d.ts +31 -0
- package/dist/sandbox/errors.d.ts +4 -0
- package/dist/sandbox/local-files.d.ts +6 -0
- package/dist/sandbox/paths.d.ts +4 -0
- package/dist/sandbox/registry.d.ts +13 -0
- package/dist/sandbox/resolve.d.ts +40 -0
- package/dist/sandbox/retry.d.ts +10 -0
- package/dist/sandbox/shell.d.ts +11 -0
- package/dist/sandbox/source-files.d.ts +13 -0
- package/dist/sandbox/types.d.ts +203 -0
- package/dist/sandbox/vercel.d.ts +37 -0
- package/dist/scoring/types.d.ts +70 -0
- package/dist/shared/aggregate.d.ts +29 -0
- package/dist/shared/aggregate.js +55 -0
- package/dist/shared/types.d.ts +27 -0
- package/dist/shared/verdict.d.ts +27 -0
- package/dist/shared/verdict.js +34 -0
- package/dist/tty-line.d.ts +6 -0
- package/dist/types.d.ts +7 -0
- package/dist/util.d.ts +22 -0
- package/docs-site/zh/concepts/adapter.mdx +7 -7
- package/docs-site/zh/concepts/assert.mdx +19 -18
- package/docs-site/zh/concepts/evals.mdx +12 -11
- package/docs-site/zh/concepts/experiment.mdx +2 -2
- package/docs-site/zh/concepts/judge.mdx +9 -9
- package/docs-site/zh/concepts/overview.mdx +3 -3
- package/docs-site/zh/concepts/tier.mdx +6 -6
- package/docs-site/zh/example/claude-code-codex-plugin.mdx +9 -5
- package/docs-site/zh/example/claude-code-codex-skill.mdx +12 -7
- package/docs-site/zh/guides/agent-feedback-loop.mdx +237 -78
- package/docs-site/zh/guides/connect-otel.mdx +3 -3
- package/docs-site/zh/guides/connect-your-agent.mdx +2 -2
- package/docs-site/zh/guides/custom-reports.mdx +196 -36
- package/docs-site/zh/guides/experiments.mdx +7 -1
- package/docs-site/zh/guides/fixtures.mdx +4 -2
- package/docs-site/zh/guides/official-adapters.mdx +31 -9
- package/docs-site/zh/guides/publish-report.mdx +4 -4
- package/docs-site/zh/guides/report-components.mdx +194 -55
- package/docs-site/zh/guides/reporters.mdx +2 -2
- package/docs-site/zh/guides/results-data.mdx +61 -34
- package/docs-site/zh/guides/runner.mdx +2 -2
- package/docs-site/zh/guides/sandbox-agent.mdx +3 -2
- package/docs-site/zh/guides/{sandbox-backends.mdx → sandbox-providers.mdx} +12 -12
- package/docs-site/zh/guides/viewing-results.mdx +200 -79
- package/docs-site/zh/guides/write-experiment.mdx +9 -1
- package/docs-site/zh/guides/write-send.mdx +4 -3
- package/docs-site/zh/index.mdx +4 -3
- package/docs-site/zh/introduction.mdx +2 -1
- package/docs-site/zh/quickstart.mdx +5 -3
- package/docs-site/zh/reference/builtin-agents.mdx +38 -13
- package/docs-site/zh/reference/capabilities.mdx +1 -1
- package/docs-site/zh/reference/cli.mdx +36 -13
- package/docs-site/zh/reference/define-agent.mdx +15 -13
- package/docs-site/zh/reference/define-config.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +3 -3
- package/docs-site/zh/reference/events.mdx +9 -3
- package/docs-site/zh/reference/expect.mdx +13 -8
- package/package.json +10 -6
- package/src/agents/bub.ts +129 -42
- package/src/agents/claude-code.test.ts +152 -0
- package/src/agents/claude-code.ts +127 -13
- package/src/agents/codex.test.ts +192 -0
- package/src/agents/codex.ts +151 -12
- package/src/agents/index.ts +9 -3
- package/src/agents/manifest.ts +41 -0
- package/src/agents/plugin-config.test.ts +94 -0
- package/src/agents/shared.ts +33 -2
- package/src/agents/skills.test.ts +214 -0
- package/src/agents/skills.ts +304 -0
- package/src/agents/streaming.ts +5 -5
- package/src/agents/types.ts +59 -3
- package/src/cli.ts +74 -47
- package/src/context/context.test.ts +20 -2
- package/src/context/control-flow.ts +1 -1
- package/src/context/session.test.ts +92 -1
- package/src/context/session.ts +23 -1
- package/src/context/types.ts +4 -4
- package/src/define.test.ts +8 -8
- package/src/define.ts +11 -11
- package/src/expect/index.ts +5 -5
- package/src/i18n/en.ts +33 -15
- package/src/i18n/zh-CN.ts +37 -21
- package/src/index.ts +1 -1
- package/src/o11y/execution-tree.test.ts +452 -0
- package/src/o11y/execution-tree.ts +367 -0
- package/src/o11y/otlp/receiver.ts +1 -1
- package/src/o11y/otlp/select.ts +7 -3
- package/src/o11y/parsers/claude-code.test.ts +175 -0
- package/src/o11y/parsers/claude-code.ts +32 -0
- package/src/o11y/types.ts +6 -1
- package/src/report/aggregate.ts +26 -20
- package/src/report/built-in-user-parity.test.tsx +640 -0
- package/src/report/built-ins/experiment-comparison.tsx +19 -0
- package/src/report/built-ins/index.ts +2 -0
- package/src/report/components.tsx +225 -50
- package/src/report/compute.ts +335 -54
- package/src/report/dual-render.test.tsx +1129 -0
- package/src/report/flag.ts +1 -1
- package/src/report/format.ts +35 -2
- package/src/report/index.ts +47 -14
- package/src/report/load.ts +1 -1
- package/src/report/locale.ts +229 -0
- package/src/report/metrics.ts +39 -15
- package/src/report/primitives.tsx +100 -2
- package/src/report/react/AttemptList.tsx +113 -0
- package/src/report/react/DeltaTable.tsx +9 -6
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentList.tsx +220 -0
- package/src/report/react/GroupSummary.tsx +66 -0
- package/src/report/react/MetricBars.tsx +24 -13
- package/src/report/react/MetricLine.tsx +21 -21
- package/src/report/react/MetricMatrix.tsx +8 -4
- package/src/report/react/MetricScatter.tsx +126 -96
- package/src/report/react/MetricTable.tsx +115 -15
- package/src/report/react/RunOverview.tsx +48 -26
- package/src/report/react/Scoreboard.tsx +15 -7
- package/src/report/react/cell.tsx +18 -9
- package/src/{view/app/lib/chart.ts → report/react/chart-math.ts} +2 -6
- package/src/report/react/colors.ts +24 -15
- package/src/report/react/enhance.js +168 -0
- package/src/report/react/fixtures.ts +169 -34
- package/src/report/react/format.ts +1 -1
- package/src/report/react/index.tsx +21 -6
- package/src/report/react/render.test.tsx +180 -31
- package/src/report/react/styles.css +432 -213
- package/src/report/report.test.ts +398 -94
- package/src/report/report.ts +29 -11
- package/src/report/text/faces.ts +344 -96
- package/src/report/text/layout.ts +76 -11
- package/src/report/text/table.ts +98 -0
- package/src/report/tree.ts +103 -20
- package/src/report/types.ts +184 -25
- package/src/report/web.ts +46 -19
- package/src/results/annotated-source.test.ts +146 -0
- package/src/results/annotated-source.ts +128 -0
- package/src/results/attempt-evidence.test.ts +158 -0
- package/src/results/attempt-evidence.ts +158 -0
- package/src/results/attempt-source.ts +48 -0
- package/src/results/copy.ts +154 -139
- package/src/results/format.ts +54 -47
- package/src/results/host-equivalence.test.ts +527 -0
- package/src/results/index.ts +56 -14
- package/src/results/locator.test.ts +198 -0
- package/src/results/locator.ts +0 -0
- package/src/results/open.ts +392 -175
- package/src/results/results.test.ts +943 -494
- package/src/results/select.ts +129 -23
- package/src/results/skipped-notice.ts +0 -0
- package/src/results/source-hash.ts +28 -0
- package/src/results/types.ts +98 -62
- package/src/results/writer.ts +304 -176
- package/src/runner/attempt.test.ts +170 -0
- package/src/runner/attempt.ts +48 -13
- package/src/runner/discover.test.ts +65 -0
- package/src/runner/discover.ts +6 -2
- package/src/runner/eval-source.test.ts +121 -0
- package/src/runner/eval-source.ts +45 -0
- package/src/runner/fingerprint.ts +54 -1
- package/src/runner/remote-sandbox.ts +1 -1
- package/src/runner/report.test.ts +6 -8
- package/src/runner/report.ts +5 -4
- package/src/runner/reporters/artifacts.ts +27 -41
- package/src/runner/reporters/braintrust.test.ts +3 -3
- package/src/runner/reporters/braintrust.ts +1 -1
- package/src/runner/reporters/console.ts +6 -6
- package/src/runner/reporters/json.ts +4 -4
- package/src/runner/reporters/live.test.ts +56 -0
- package/src/runner/reporters/live.ts +36 -10
- package/src/runner/reporters/quiet.test.ts +66 -0
- package/src/runner/reporters/quiet.ts +49 -0
- package/src/runner/reporters/shared.ts +5 -5
- package/src/runner/reporters/table.ts +17 -17
- package/src/runner/run.ts +61 -102
- package/src/runner/types.ts +43 -32
- package/src/sandbox/checkpoint.ts +1 -1
- package/src/sandbox/docker.ts +15 -2
- package/src/sandbox/e2b.ts +11 -5
- package/src/sandbox/errors.ts +13 -0
- package/src/sandbox/index.ts +3 -3
- package/src/sandbox/local-files.ts +1 -1
- package/src/sandbox/registry.ts +6 -5
- package/src/sandbox/resolve.ts +38 -23
- package/src/sandbox/retry.test.ts +68 -0
- package/src/sandbox/retry.ts +44 -0
- package/src/sandbox/types.ts +24 -24
- package/src/sandbox/vercel.ts +17 -3
- package/src/scoring/collector.ts +3 -3
- package/src/scoring/judge.ts +2 -2
- package/src/scoring/scoped.ts +13 -1
- package/src/scoring/types.ts +7 -7
- package/src/scoring/verdict.ts +4 -4
- package/src/shared/aggregate.ts +23 -3
- package/src/shared/types.ts +2 -2
- package/src/shared/{outcome.ts → verdict.ts} +15 -15
- package/src/show/compose.ts +12 -132
- package/src/show/index.ts +119 -95
- package/src/show/render.ts +462 -159
- package/src/show/show.test.ts +471 -253
- package/src/source-loc.ts +1 -1
- package/src/tty-line.ts +32 -0
- package/src/types.ts +2 -2
- package/src/util.test.ts +21 -1
- package/src/util.ts +5 -1
- package/src/view/app/App.tsx +33 -198
- package/src/view/app/components/AttemptModal.tsx +6 -6
- package/src/view/app/components/CodeView.tsx +2 -2
- package/src/view/app/components/CopyControls.tsx +32 -36
- package/src/view/app/components/SkippedRunsBanner.tsx +140 -0
- package/src/view/app/components/ui/badge.tsx +1 -1
- package/src/view/app/components/ui/dialog.tsx +2 -2
- package/src/view/app/i18n.ts +40 -31
- package/src/view/app/lib/artifact-url.ts +1 -1
- package/src/view/app/lib/attempt-route.test.ts +37 -47
- package/src/view/app/lib/attempt-route.ts +26 -26
- package/src/view/app/lib/rows.ts +9 -112
- package/src/view/app/lib/verdict.ts +25 -0
- package/src/view/app/main.tsx +9 -12
- package/src/view/app/pages/RunsPage.tsx +4 -4
- package/src/view/app/pages/TracesPage.tsx +3 -3
- package/src/view/app/shared.ts +0 -1
- package/src/view/app/types.ts +6 -51
- package/src/view/artifact-serving.test.ts +136 -0
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +21 -21
- package/src/view/data.test.ts +214 -143
- package/src/view/data.ts +166 -120
- package/src/view/index.ts +23 -11
- package/src/view/server.ts +52 -17
- package/src/view/shared/types.ts +22 -31
- package/src/view/styles.css +117 -327
- package/src/view/view-report.test.ts +146 -98
- package/src/report/default-report.tsx +0 -222
- package/src/report/dual-face.test.tsx +0 -527
- package/src/report/react/CaseList.tsx +0 -70
- package/src/view/app/components/CostScoreChart.tsx +0 -147
- package/src/view/app/components/ExperimentTable.tsx +0 -284
- package/src/view/app/components/GroupSelector.tsx +0 -61
- package/src/view/app/components/primitives.tsx +0 -43
- package/src/view/app/lib/outcome.ts +0 -82
package/src/report/text/faces.ts
CHANGED
|
@@ -2,10 +2,15 @@
|
|
|
2
2
|
// 输出形态照 docs-site/zh/guides/report-components.mdx 的示例块;与 web 面共守
|
|
3
3
|
// 诚实契约:排序随 better、samples < total 角标、缺数据 — 不补 0、截断报剩余。
|
|
4
4
|
// 零 react、零 IO、纯同步 —— 这是 text 宿主不需要 react-dom 的那一半。
|
|
5
|
+
// chrome 文案(注脚、verdict 词、截断提示)经 ctx.locale 查 locale 字典,
|
|
6
|
+
// 默认 en 与历史输出逐字一致;数据(display、键、warnings message)不本地化。
|
|
5
7
|
|
|
6
8
|
import type {
|
|
7
|
-
|
|
9
|
+
AttemptListItem,
|
|
8
10
|
DeltaData,
|
|
11
|
+
EvalListItem,
|
|
12
|
+
ExperimentListItem,
|
|
13
|
+
GroupSummaryData,
|
|
9
14
|
LineData,
|
|
10
15
|
MatrixData,
|
|
11
16
|
MetricColumn,
|
|
@@ -15,12 +20,27 @@ import type {
|
|
|
15
20
|
TableData,
|
|
16
21
|
} from "../types.ts";
|
|
17
22
|
import type { TextContext } from "../tree.ts";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
23
|
+
import type { TableColumn, TableRow } from "../primitives.tsx";
|
|
24
|
+
import {
|
|
25
|
+
attemptItemReason,
|
|
26
|
+
formatDurationMs,
|
|
27
|
+
formatMetricValue,
|
|
28
|
+
formatPlainNumber,
|
|
29
|
+
formatUSD,
|
|
30
|
+
verdictMark,
|
|
31
|
+
} from "../format.ts";
|
|
32
|
+
import { countText, localeText, resolveMetricLabel, type ReportLocale } from "../locale.ts";
|
|
33
|
+
import { indentBlock, padDisplay, textBar, wrapDisplay } from "./layout.ts";
|
|
34
|
+
import { renderTableText } from "./table.ts";
|
|
20
35
|
import { renderCharPlot, renderCoordinateTable, type PlotPoint } from "./plot.ts";
|
|
21
36
|
|
|
22
37
|
const MISSING_MARK = "—";
|
|
23
38
|
|
|
39
|
+
/** 缺数据文案随 locale(en = "no data",与 MISSING_TEXT 一致)。 */
|
|
40
|
+
function missingText(locale: ReportLocale): string {
|
|
41
|
+
return localeText(locale, "cell.missing");
|
|
42
|
+
}
|
|
43
|
+
|
|
24
44
|
/** 格子的文本形态:缺数据 —,覆盖不全带 samples/total 角标。 */
|
|
25
45
|
export function cellText(cell: { value: number | null; display: string; samples: number; total: number }): string {
|
|
26
46
|
if (cell.value === null) return MISSING_MARK;
|
|
@@ -29,23 +49,27 @@ export function cellText(cell: { value: number | null; display: string; samples:
|
|
|
29
49
|
|
|
30
50
|
// ───────────────────────── RunOverview ─────────────────────────
|
|
31
51
|
|
|
32
|
-
export function overviewText(data: OverviewData): string {
|
|
52
|
+
export function overviewText(data: OverviewData, ctx: TextContext): string {
|
|
33
53
|
const { totals, snapshots } = data;
|
|
54
|
+
const locale = ctx.locale;
|
|
34
55
|
const runs = new Set(snapshots.map((s) => s.startedAt)).size;
|
|
35
56
|
const latest = snapshots.map((s) => s.startedAt).sort().at(-1);
|
|
36
57
|
const head = [
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
58
|
+
countText(locale, "overview.experiments", snapshots.length),
|
|
59
|
+
localeText(locale, "overview.evalsCount", { n: totals.evals }),
|
|
60
|
+
localeText(locale, "overview.attemptsCount", { n: totals.attempts }),
|
|
61
|
+
// 通过率只渲染 computeCell 算好的同一个 MetricCell(cellText 复用缺数据/coverage 角标语义),
|
|
62
|
+
// 不从 passed/failed/errored 现场重算——与 web 面 RunOverview 同一份数据同一种读法。
|
|
63
|
+
`${localeText(locale, "overview.passRate")} ${cellText(totals.passRate)}`,
|
|
64
|
+
countText(locale, "composedFrom", runs),
|
|
65
|
+
...(latest ? [localeText(locale, "latestRun", { run: latest })] : []),
|
|
42
66
|
].join(" · ");
|
|
43
67
|
const tallies = [
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
totals.costUSD === null ?
|
|
68
|
+
`${localeText(locale, "verdict.passed")} ${totals.passed}`,
|
|
69
|
+
`${localeText(locale, "verdict.failed")} ${totals.failed}`,
|
|
70
|
+
`${localeText(locale, "verdict.errored")} ${totals.errored}`,
|
|
71
|
+
`${localeText(locale, "verdict.skipped")} ${totals.skipped}`,
|
|
72
|
+
totals.costUSD === null ? missingText(locale) : formatUSD(totals.costUSD),
|
|
49
73
|
formatDurationMs(totals.durationMs),
|
|
50
74
|
].join(" · ");
|
|
51
75
|
const lines = [head, tallies];
|
|
@@ -53,23 +77,93 @@ export function overviewText(data: OverviewData): string {
|
|
|
53
77
|
return lines.join("\n");
|
|
54
78
|
}
|
|
55
79
|
|
|
80
|
+
// ───────────────────────── GroupSummary ─────────────────────────
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 一至两行:头行是通过率(GroupSummaryData.passRate.display,不重算比例)+ experiment/eval 数 +
|
|
84
|
+
* failed(+ errored,非零才列,与旧 GroupSelector 卡片一致)+ 总成本;第二行(有则加)是最后运行时间。
|
|
85
|
+
* 不依赖固定网格宽度,窄终端自然换行。
|
|
86
|
+
*/
|
|
87
|
+
export function groupSummaryText(data: GroupSummaryData, ctx: TextContext): string {
|
|
88
|
+
const locale = ctx.locale;
|
|
89
|
+
const head = [
|
|
90
|
+
`${localeText(locale, "overview.passRate")} ${cellText(data.passRate)}`,
|
|
91
|
+
countText(locale, "overview.experiments", data.experiments),
|
|
92
|
+
localeText(locale, "overview.evalsCount", { n: data.evals }),
|
|
93
|
+
`${localeText(locale, "verdict.failed")} ${data.verdicts.failed}`,
|
|
94
|
+
...(data.verdicts.errored > 0 ? [`${localeText(locale, "verdict.errored")} ${data.verdicts.errored}`] : []),
|
|
95
|
+
data.totalCostUSD === null ? missingText(locale) : formatUSD(data.totalCostUSD),
|
|
96
|
+
].join(" · ");
|
|
97
|
+
const lines = [head];
|
|
98
|
+
if (data.lastRunAt) lines.push(localeText(locale, "latestRun", { run: data.lastRunAt }));
|
|
99
|
+
return lines.join("\n");
|
|
100
|
+
}
|
|
101
|
+
|
|
56
102
|
// ───────────────────────── MetricTable ─────────────────────────
|
|
57
103
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
104
|
+
/** verdict 计票的紧凑文案("3 passed / 1 failed"):非零判定逐个列,全部为零如实 —。 */
|
|
105
|
+
export function verdictTallyText(
|
|
106
|
+
verdicts: NonNullable<NonNullable<TableData["rows"][number]["meta"]>["verdicts"]>,
|
|
107
|
+
locale: ReportLocale,
|
|
108
|
+
): string {
|
|
109
|
+
const parts: string[] = [];
|
|
110
|
+
for (const kind of ["passed", "failed", "errored", "skipped"] as const) {
|
|
111
|
+
if (verdicts[kind] > 0) parts.push(`${verdicts[kind]} ${localeText(locale, `verdict.${kind}`)}`);
|
|
112
|
+
}
|
|
113
|
+
return parts.length > 0 ? parts.join(" / ") : MISSING_MARK;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function tableText(data: TableData, ctx: TextContext): string {
|
|
117
|
+
const locale = ctx.locale;
|
|
118
|
+
// meta 在场时补 Model / Agent / Verdicts 列(rows: "experiment" 的榜单 parity);
|
|
119
|
+
// 列序对齐 view 原生榜单:experiment、model、agent、指标列…、verdicts
|
|
120
|
+
const hasMeta = data.rows.some((row) => row.meta !== undefined);
|
|
121
|
+
const hasModel = data.rows.some((row) => row.meta?.model !== undefined);
|
|
122
|
+
const hasVerdicts = data.rows.some((row) => row.meta?.verdicts !== undefined);
|
|
123
|
+
const columns: TableColumn[] = [
|
|
124
|
+
{ key: "dimension", header: data.dimension },
|
|
125
|
+
...(hasMeta && hasModel ? [{ key: "model", header: localeText(locale, "table.model") }] : []),
|
|
126
|
+
...(hasMeta ? [{ key: "agent", header: localeText(locale, "table.agent") }] : []),
|
|
127
|
+
// 指标列的键用序号,不用 metric name —— 维度名、"model" 这些键都在同一个命名空间里,
|
|
128
|
+
// 指标恰好叫 "agent" 时不能把 meta 列顶掉。
|
|
129
|
+
...data.columns.map((c, i) => ({ key: `metric${i}`, header: resolveMetricLabel(c.label, locale, c.key) })),
|
|
130
|
+
...(hasVerdicts ? [{ key: "verdicts", header: localeText(locale, "table.verdicts") }] : []),
|
|
131
|
+
];
|
|
132
|
+
const rows: TableRow[] = data.rows.map((row) => {
|
|
133
|
+
const cells: Record<string, string | null> = { dimension: row.key };
|
|
134
|
+
if (hasMeta && hasModel) cells.model = row.meta?.model ?? null;
|
|
135
|
+
if (hasMeta) cells.agent = row.meta?.agent ?? null;
|
|
136
|
+
data.columns.forEach((col, i) => {
|
|
63
137
|
const cell = (row.cells as Record<string, TableData["rows"][number]["cells"][string]>)[col.key];
|
|
64
|
-
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
|
|
138
|
+
cells[`metric${i}`] = cell ? cellText(cell) : null;
|
|
139
|
+
});
|
|
140
|
+
if (hasVerdicts) cells.verdicts = row.meta?.verdicts ? verdictTallyText(row.meta.verdicts, locale) : null;
|
|
141
|
+
return { key: row.key, cells };
|
|
142
|
+
});
|
|
143
|
+
const table = renderTableText({ columns, rows, locale }, ctx);
|
|
144
|
+
|
|
145
|
+
// rows: "experiment" 专属的行摘要(eval/attempt 数 + 最后运行时间):表格列已经挤满
|
|
146
|
+
// dimension/model/agent/指标/verdicts,这几个数字挤进行键下面单独一行,与 web 面
|
|
147
|
+
// 「行键下的小 sub-line」同一份信息、同一种「不进列」的取舍。
|
|
148
|
+
const metaLines: string[] = [];
|
|
149
|
+
for (const row of data.rows) {
|
|
150
|
+
if (row.meta?.evals === undefined) continue;
|
|
151
|
+
const parts = [localeText(locale, "overview.evalsCount", { n: row.meta.evals })];
|
|
152
|
+
if (row.meta.attempts !== undefined && row.meta.attempts > row.meta.evals) {
|
|
153
|
+
parts.push(localeText(locale, "overview.attemptsCount", { n: row.meta.attempts }));
|
|
154
|
+
}
|
|
155
|
+
if (row.meta.lastRunAt) parts.push(localeText(locale, "latestRun", { run: row.meta.lastRunAt }));
|
|
156
|
+
metaLines.push(` ${row.key}: ${parts.join(" · ")}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const blocks = [table, ...(metaLines.length > 0 ? [metaLines.join("\n")] : [])];
|
|
160
|
+
return blocks.join("\n\n");
|
|
68
161
|
}
|
|
69
162
|
|
|
70
163
|
// ───────────────────────── MetricMatrix ─────────────────────────
|
|
71
164
|
|
|
72
|
-
export function matrixText(data: MatrixData): string {
|
|
165
|
+
export function matrixText(data: MatrixData, ctx: TextContext): string {
|
|
166
|
+
// 表体全是维度键与 display,没有 chrome 文案;"next:" 是命令提示,不本地化。
|
|
73
167
|
const rowKeys: string[] = [];
|
|
74
168
|
const columnKeys: string[] = [];
|
|
75
169
|
const byPosition = new Map<string, MatrixData["cells"][number]["cell"]>();
|
|
@@ -78,15 +172,19 @@ export function matrixText(data: MatrixData): string {
|
|
|
78
172
|
if (!columnKeys.includes(entry.column)) columnKeys.push(entry.column);
|
|
79
173
|
byPosition.set(JSON.stringify([entry.row, entry.column]), entry.cell);
|
|
80
174
|
}
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
175
|
+
const columns: TableColumn[] = [
|
|
176
|
+
{ key: "dimension", header: data.rows },
|
|
177
|
+
...columnKeys.map((column, i) => ({ key: `column${i}`, header: column })),
|
|
178
|
+
];
|
|
179
|
+
const rows: TableRow[] = rowKeys.map((row) => {
|
|
180
|
+
const cells: Record<string, string | null> = { dimension: row };
|
|
181
|
+
columnKeys.forEach((column, i) => {
|
|
85
182
|
const cell = byPosition.get(JSON.stringify([row, column]));
|
|
86
|
-
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
|
|
183
|
+
cells[`column${i}`] = cell ? cellText(cell) : null; // 稀疏格子在文本里以 — 呈现,不编数
|
|
184
|
+
});
|
|
185
|
+
return { key: row, cells };
|
|
186
|
+
});
|
|
187
|
+
const table = renderTableText({ columns, rows, locale: ctx.locale }, ctx);
|
|
90
188
|
|
|
91
189
|
// 下钻命令:行维度是 eval 时,指向最值得看的一行(先挑有缺格的,再挑按 better 最差的)
|
|
92
190
|
if (data.rows !== "eval" || rowKeys.length === 0) return table;
|
|
@@ -166,54 +264,70 @@ export function barsText(data: MatrixData): string {
|
|
|
166
264
|
|
|
167
265
|
// ───────────────────────── Scoreboard ─────────────────────────
|
|
168
266
|
|
|
169
|
-
export function scoreboardText(data: ScoreboardData): string {
|
|
267
|
+
export function scoreboardText(data: ScoreboardData, ctx: TextContext): string {
|
|
268
|
+
const locale = ctx.locale;
|
|
170
269
|
const subjectKeys: string[] = [];
|
|
171
270
|
for (const row of data.rows) {
|
|
172
271
|
for (const subject of row.subjects) {
|
|
173
272
|
if (!subjectKeys.includes(subject.key)) subjectKeys.push(subject.key);
|
|
174
273
|
}
|
|
175
274
|
}
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
275
|
+
const columns: TableColumn[] = [
|
|
276
|
+
{ key: "dimension", header: data.dimension },
|
|
277
|
+
{ key: "total", header: localeText(locale, "scoreboard.totalText") },
|
|
278
|
+
...subjectKeys.map((key, i) => ({ key: `subject${i}`, header: key })),
|
|
279
|
+
];
|
|
280
|
+
const rows: TableRow[] = data.rows.map((row) => {
|
|
281
|
+
const cells: Record<string, string | null> = {
|
|
282
|
+
dimension: row.key,
|
|
283
|
+
total: `${row.total.display}/${data.fullMarks}`,
|
|
284
|
+
};
|
|
285
|
+
subjectKeys.forEach((key, i) => {
|
|
181
286
|
const subject = row.subjects.find((s) => s.key === key);
|
|
182
|
-
if (!subject)
|
|
287
|
+
if (!subject) {
|
|
288
|
+
cells[`subject${i}`] = null;
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
183
291
|
const score = `${formatPlainNumber(subject.earned)}/${formatPlainNumber(subject.possible)}`;
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
292
|
+
cells[`subject${i}`] =
|
|
293
|
+
subject.missing > 0
|
|
294
|
+
? `${score} ${localeText(locale, "scoreboard.missingText", { n: subject.missing })}`
|
|
295
|
+
: score;
|
|
296
|
+
});
|
|
297
|
+
return { key: row.key, cells };
|
|
298
|
+
});
|
|
299
|
+
const table = renderTableText({ columns, rows, locale }, ctx);
|
|
188
300
|
if (data.weights.length === 0) return table;
|
|
189
301
|
// 实际生效的权重表 —— 成绩单可审计
|
|
190
302
|
const weights = data.weights.map((w) => `${w.prefix} ×${w.weight}`).join(" · ");
|
|
191
|
-
return `${table}\
|
|
303
|
+
return `${table}\n${localeText(locale, "scoreboard.weights")} ${weights} · ${localeText(locale, "scoreboard.othersWeight")}`;
|
|
192
304
|
}
|
|
193
305
|
|
|
194
306
|
// ───────────────────────── MetricScatter ─────────────────────────
|
|
195
307
|
|
|
196
308
|
const POINT_MARKS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
197
309
|
|
|
198
|
-
function axisLabel(col: MetricColumn): string {
|
|
199
|
-
return col.label;
|
|
310
|
+
function axisLabel(col: MetricColumn, locale: ReportLocale): string {
|
|
311
|
+
return resolveMetricLabel(col.label, locale, col.key);
|
|
200
312
|
}
|
|
201
313
|
|
|
202
314
|
export function scatterText(data: ScatterData, ctx: TextContext): string {
|
|
315
|
+
const locale = ctx.locale;
|
|
203
316
|
const drawable = data.rows.filter((r) => r.x.value !== null && r.y.value !== null);
|
|
204
317
|
const missing = data.rows.length - drawable.length;
|
|
205
318
|
const footnotes: string[] = [];
|
|
206
|
-
if (missing > 0) footnotes.push(
|
|
319
|
+
if (missing > 0) footnotes.push(countText(locale, "pointsMissing", missing));
|
|
207
320
|
|
|
321
|
+
const axes = { x: axisLabel(data.x, locale), y: axisLabel(data.y, locale) };
|
|
322
|
+
// 0 个可画点:x/y 指标没有可用数据(与 web 面同一事实)。
|
|
208
323
|
if (drawable.length === 0) {
|
|
209
|
-
return [
|
|
324
|
+
return [localeText(locale, "scatter.noData", axes), ...footnotes].join("\n");
|
|
210
325
|
}
|
|
211
|
-
|
|
212
326
|
// 点太密排不下时降级为坐标表,不硬挤
|
|
213
327
|
if (drawable.length > POINT_MARKS.length || ctx.width < 44) {
|
|
214
328
|
const table = renderCoordinateTable(
|
|
215
329
|
drawable.map((r) => ({ key: r.key, x: r.x.display, y: r.y.display })),
|
|
216
|
-
{ key: data.points, x: axisLabel(data.x), y: axisLabel(data.y) },
|
|
330
|
+
{ key: data.points, x: axisLabel(data.x, locale), y: axisLabel(data.y, locale) },
|
|
217
331
|
);
|
|
218
332
|
return [table, ...footnotes].join("\n");
|
|
219
333
|
}
|
|
@@ -238,26 +352,27 @@ export function scatterText(data: ScatterData, ctx: TextContext): string {
|
|
|
238
352
|
width: ctx.width,
|
|
239
353
|
points,
|
|
240
354
|
lines: [...bySeries.values()].filter((l) => l.length > 1),
|
|
241
|
-
xLabel: `${axisLabel(data.x)}${invertX ?
|
|
242
|
-
yLabel: axisLabel(data.y),
|
|
355
|
+
xLabel: `${axisLabel(data.x, locale)}${invertX ? ` ${localeText(locale, "scatter.axisReversed")}` : ""}`,
|
|
356
|
+
yLabel: axisLabel(data.y, locale),
|
|
243
357
|
formatX: (v) => formatMetricValue(v, data.x.unit),
|
|
244
358
|
formatY: (v) => formatMetricValue(v, data.y.unit),
|
|
245
359
|
invertX,
|
|
246
360
|
invertY: data.y.better === "lower",
|
|
247
361
|
});
|
|
248
362
|
const legend = drawable.map((r, i) => `${POINT_MARKS[i]} ${r.key}`).join(" ");
|
|
249
|
-
return [plot, "",
|
|
363
|
+
return [plot, "", localeText(locale, "scatter.betterUpperRight"), legend, ...footnotes].join("\n");
|
|
250
364
|
}
|
|
251
365
|
|
|
252
366
|
// ───────────────────────── MetricLine ─────────────────────────
|
|
253
367
|
|
|
254
368
|
export function lineText(data: LineData, ctx: TextContext): string {
|
|
369
|
+
const locale = ctx.locale;
|
|
255
370
|
const drawable = data.rows.filter((r) => r.x !== null && r.y.value !== null);
|
|
256
371
|
const missing = data.rows.length - drawable.length;
|
|
257
372
|
const footnotes: string[] = [];
|
|
258
|
-
if (missing > 0) footnotes.push(
|
|
373
|
+
if (missing > 0) footnotes.push(countText(locale, "pointsMissing", missing));
|
|
259
374
|
|
|
260
|
-
if (drawable.length === 0) return [
|
|
375
|
+
if (drawable.length === 0) return [missingText(locale), ...footnotes].join("\n");
|
|
261
376
|
|
|
262
377
|
// 系列 → 字母;无系列 = 单系列
|
|
263
378
|
const seriesKeys: string[] = [];
|
|
@@ -269,7 +384,7 @@ export function lineText(data: LineData, ctx: TextContext): string {
|
|
|
269
384
|
if (seriesKeys.length > POINT_MARKS.length || ctx.width < 44) {
|
|
270
385
|
const table = renderCoordinateTable(
|
|
271
386
|
drawable.map((r) => ({ key: r.series ? `${r.key} (${r.series})` : r.key, x: r.xDisplay, y: r.y.display })),
|
|
272
|
-
{ key: "experiment", x: data.x.label, y: axisLabel(data.y) },
|
|
387
|
+
{ key: "experiment", x: data.x.label, y: axisLabel(data.y, locale) },
|
|
273
388
|
);
|
|
274
389
|
return [table, ...footnotes].join("\n");
|
|
275
390
|
}
|
|
@@ -292,65 +407,198 @@ export function lineText(data: LineData, ctx: TextContext): string {
|
|
|
292
407
|
points,
|
|
293
408
|
lines: lines.filter((l) => l.length > 1),
|
|
294
409
|
xLabel: data.x.label,
|
|
295
|
-
yLabel: axisLabel(data.y),
|
|
410
|
+
yLabel: axisLabel(data.y, locale),
|
|
296
411
|
formatX: (v) => formatMetricValue(v, data.x.unit),
|
|
297
412
|
formatY: (v) => formatMetricValue(v, data.y.unit),
|
|
298
413
|
invertY: data.y.better === "lower",
|
|
299
414
|
});
|
|
300
415
|
const legend = seriesKeys
|
|
301
|
-
.map((key, i) => `${POINT_MARKS[i]} ${key === "" ? data.y
|
|
416
|
+
.map((key, i) => `${POINT_MARKS[i]} ${key === "" ? axisLabel(data.y, locale) : key}`)
|
|
302
417
|
.join(" ");
|
|
303
418
|
return [plot, "", legend, ...footnotes].join("\n");
|
|
304
419
|
}
|
|
305
420
|
|
|
306
421
|
// ───────────────────────── DeltaTable ─────────────────────────
|
|
307
422
|
|
|
308
|
-
export function deltaText(data: DeltaData): string {
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
423
|
+
export function deltaText(data: DeltaData, ctx: TextContext): string {
|
|
424
|
+
const columns: TableColumn[] = [
|
|
425
|
+
{ key: "pair", header: "pair" },
|
|
426
|
+
...data.columns.map((c, i) => ({ key: `metric${i}`, header: resolveMetricLabel(c.label, ctx.locale, c.key) })),
|
|
427
|
+
];
|
|
428
|
+
const rows: TableRow[] = data.rows.map((row) => {
|
|
429
|
+
const cells: Record<string, string | null> = { pair: row.key };
|
|
430
|
+
data.columns.forEach((col, i) => {
|
|
313
431
|
const cell = (row.cells as Record<string, DeltaData["rows"][number]["cells"][string]>)[col.key];
|
|
314
|
-
if (!cell)
|
|
432
|
+
if (!cell) {
|
|
433
|
+
cells[`metric${i}`] = null;
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
315
436
|
const a = cell.a.value === null ? MISSING_MARK : cell.a.display;
|
|
316
437
|
const b = cell.b.value === null ? MISSING_MARK : cell.b.display;
|
|
317
|
-
|
|
318
|
-
})
|
|
319
|
-
|
|
320
|
-
|
|
438
|
+
cells[`metric${i}`] = `${a} → ${b} ${cell.display}`;
|
|
439
|
+
});
|
|
440
|
+
return { key: row.key, cells };
|
|
441
|
+
});
|
|
442
|
+
return renderTableText({ columns, rows, locale: ctx.locale }, ctx);
|
|
321
443
|
}
|
|
322
444
|
|
|
323
|
-
// ─────────────────────────
|
|
445
|
+
// ───────────────────────── 实体列表(ExperimentList / EvalList / AttemptList)─────────────────────────
|
|
446
|
+
//
|
|
447
|
+
// 三面共用的紧凑标记:`locator✓`(判定符紧跟 locator,中间不留空格)。
|
|
448
|
+
// ExperimentList / EvalList 逐 attempt 只列这一个标记 + 各自的原因/耗时摘要,不重复整段
|
|
449
|
+
// niceeval show 命令;要看某个 attempt 的完整证据,agent 自己拼 `niceeval show <locator>`——
|
|
450
|
+
// 命令模板只在 AttemptList(叶子层)展示完整断言明细时才值得,不在中间层重复。
|
|
324
451
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
452
|
+
function locatorBadge(item: { locator: string; verdict: AttemptListItem["verdict"] }): string {
|
|
453
|
+
return `${item.locator}${verdictMark(item.verdict)}`;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// ── ExperimentList ──
|
|
457
|
+
|
|
458
|
+
function experimentSummaryTable(items: ExperimentListItem[], ctx: TextContext): string {
|
|
459
|
+
const locale = ctx.locale;
|
|
460
|
+
const compact = ctx.width < 100;
|
|
461
|
+
const columns: TableColumn[] = [
|
|
462
|
+
{ key: "experiment", header: compact && locale === "en" ? "Exp." : localeText(locale, "experimentList.experiment") },
|
|
463
|
+
{ key: "model", header: localeText(locale, "table.model") },
|
|
464
|
+
{ key: "agent", header: localeText(locale, "table.agent") },
|
|
465
|
+
{ key: "duration", header: compact && locale === "en" ? "Avg" : localeText(locale, "experimentList.avgDuration"), align: "right" },
|
|
466
|
+
{ key: "passRate", header: compact && locale === "en" ? "Pass" : localeText(locale, "experimentList.passRate"), align: "right" },
|
|
467
|
+
{ key: "result", header: localeText(locale, "experimentList.result") },
|
|
468
|
+
{ key: "tokens", header: localeText(locale, "experimentList.tokens"), align: "right" },
|
|
469
|
+
{ key: "cost", header: compact && locale === "en" ? "Cost" : localeText(locale, "experimentList.estimatedCost"), align: "right" },
|
|
470
|
+
];
|
|
471
|
+
const rows: TableRow[] = items.map((item) => ({
|
|
472
|
+
key: item.experimentId,
|
|
473
|
+
cells: {
|
|
474
|
+
experiment: item.experimentId,
|
|
475
|
+
model: item.model ?? localeText(locale, "experimentList.defaultModel"),
|
|
476
|
+
agent: item.agent,
|
|
477
|
+
duration: cellText(item.duration),
|
|
478
|
+
passRate: cellText(item.passRate),
|
|
479
|
+
result: verdictTallyText(item.verdicts, locale),
|
|
480
|
+
tokens: cellText(item.tokens),
|
|
481
|
+
cost: cellText(item.cost),
|
|
482
|
+
},
|
|
483
|
+
}));
|
|
484
|
+
const metadata = items.flatMap((item) =>
|
|
485
|
+
wrapDisplay(
|
|
486
|
+
`${item.experimentId}: ${localeText(locale, "overview.evalsCount", { n: item.evals })} · ${localeText(locale, "overview.attemptsCount", { n: item.attempts })} · ${item.lastRunAt}`,
|
|
487
|
+
Math.max(8, ctx.width - 2),
|
|
488
|
+
).map((line) => ` ${line}`),
|
|
489
|
+
);
|
|
490
|
+
return [renderTableText({ columns, rows, locale }, ctx), metadata.join("\n")].join("\n");
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function experimentDetailTable(item: ExperimentListItem, ctx: TextContext): string {
|
|
494
|
+
const locale = ctx.locale;
|
|
495
|
+
const columns: TableColumn[] = [
|
|
496
|
+
{ key: "status", header: localeText(locale, "experimentList.status") },
|
|
497
|
+
{ key: "entity", header: localeText(locale, "experimentList.evalAttempt") },
|
|
498
|
+
{ key: "result", header: localeText(locale, "experimentList.result") },
|
|
499
|
+
{ key: "duration", header: localeText(locale, "experimentList.duration"), align: "right" },
|
|
500
|
+
{ key: "cost", header: localeText(locale, "experimentList.cost"), align: "right" },
|
|
501
|
+
];
|
|
502
|
+
const rows: TableRow[] = item.evalRows.flatMap((row) => {
|
|
503
|
+
const parent: TableRow = {
|
|
504
|
+
key: row.evalId,
|
|
505
|
+
cells: {
|
|
506
|
+
status: `${verdictMark(row.verdict)} ${localeText(locale, `verdict.${row.verdict}`)}`,
|
|
507
|
+
entity: row.evalId,
|
|
508
|
+
result: "",
|
|
509
|
+
duration: "",
|
|
510
|
+
cost: "",
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
const attempts: TableRow[] = row.attempts.map((attempt, index) => ({
|
|
514
|
+
key: attempt.locator,
|
|
515
|
+
cells: {
|
|
516
|
+
status: ` ${verdictMark(attempt.verdict)}`,
|
|
517
|
+
entity: `${index === row.attempts.length - 1 ? "└─" : "├─"} ${attempt.locator}`,
|
|
518
|
+
result: attemptItemReason(attempt) ?? MISSING_MARK,
|
|
519
|
+
duration: attempt.verdict === "skipped" && attempt.durationMs === 0 ? null : formatDurationMs(attempt.durationMs),
|
|
520
|
+
cost: attempt.costUSD === undefined ? null : formatUSD(attempt.costUSD),
|
|
521
|
+
},
|
|
522
|
+
}));
|
|
523
|
+
return [parent, ...attempts];
|
|
524
|
+
});
|
|
525
|
+
const flags = item.flags && Object.keys(item.flags).length > 0
|
|
526
|
+
? `${localeText(locale, "experimentList.flags")} ${Object.entries(item.flags).map(([key, value]) => `${key}=${String(value)}`).join(" · ")}`
|
|
527
|
+
: undefined;
|
|
528
|
+
return [item.experimentId, flags, renderTableText({ columns, rows, locale }, ctx)].filter(Boolean).join("\n");
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export function experimentListText(items: ExperimentListItem[], ctx: TextContext): string {
|
|
532
|
+
if (items.length === 0) return localeText(ctx.locale, "attemptList.empty");
|
|
533
|
+
return [experimentSummaryTable(items, ctx), ...items.map((item) => experimentDetailTable(item, ctx))].join("\n\n");
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// ── EvalList ──
|
|
537
|
+
|
|
538
|
+
function evalListAttemptLine(item: AttemptListItem): string {
|
|
539
|
+
const reason = attemptItemReason(item);
|
|
540
|
+
return ` ${locatorBadge(item)}${reason ? ` · ${reason}` : ""}`;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export function evalListText(items: EvalListItem[], ctx: TextContext): string {
|
|
544
|
+
const locale = ctx.locale;
|
|
545
|
+
if (items.length === 0) return localeText(locale, "attemptList.empty");
|
|
546
|
+
const blocks = items.map((item) => {
|
|
547
|
+
const identity = `${item.evalId} · ${item.experimentId} · ${localeText(locale, `verdict.${item.verdict}`)}`;
|
|
548
|
+
const summary = [
|
|
549
|
+
localeText(locale, "attemptList.score", { score: cellText(item.score) }),
|
|
550
|
+
localeText(locale, "overview.attemptsCount", { n: item.attempts.length }),
|
|
551
|
+
`${formatDurationMs(item.duration.value ?? 0)} avg`,
|
|
552
|
+
item.cost.value === null ? `${missingText(locale)} avg` : `${formatUSD(item.cost.value)} avg`,
|
|
335
553
|
].join(" · ");
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
|
|
554
|
+
const attemptLines = item.attempts.map(evalListAttemptLine);
|
|
555
|
+
return [identity, ` ${summary}`, ...attemptLines].join("\n");
|
|
556
|
+
});
|
|
557
|
+
return blocks.join("\n\n");
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ── AttemptList ──
|
|
561
|
+
|
|
562
|
+
/** 一个 AttemptListItem 的完整 text 卡片:判定符 + locator + 身份 + 耗时/成本,
|
|
563
|
+
* 然后逐条断言(gate 与 soft 都列,与 web 面的 AttemptRow 同一份材料)。 */
|
|
564
|
+
function attemptListItemText(item: AttemptListItem, ctx: TextContext, locale: ReportLocale): string {
|
|
565
|
+
const head = [
|
|
566
|
+
`${verdictMark(item.verdict)} ${item.locator}`,
|
|
567
|
+
item.evalId,
|
|
568
|
+
item.experimentId,
|
|
569
|
+
formatDurationMs(item.durationMs),
|
|
570
|
+
...(item.costUSD !== undefined ? [formatUSD(item.costUSD)] : []),
|
|
571
|
+
].join(" · ");
|
|
572
|
+
const lines = [head];
|
|
573
|
+
if (item.error) {
|
|
574
|
+
lines.push(indentBlock(wrapDisplay(item.error, ctx.width - 4).join("\n"), " "));
|
|
350
575
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
576
|
+
for (const assertion of item.assertions) {
|
|
577
|
+
const scoreText =
|
|
578
|
+
assertion.threshold !== undefined
|
|
579
|
+
? `${formatPlainNumber(assertion.score)}/${formatPlainNumber(assertion.threshold)}`
|
|
580
|
+
: formatPlainNumber(assertion.score);
|
|
581
|
+
lines.push(
|
|
582
|
+
` ${assertion.severity} ${assertion.name} · ${localeText(locale, `verdict.${assertion.passed ? "passed" : "failed"}`)}${assertion.severity === "soft" ? ` ${scoreText}` : ""}`,
|
|
583
|
+
);
|
|
584
|
+
if (assertion.detail) lines.push(indentBlock(wrapDisplay(assertion.detail, ctx.width - 4).join("\n"), " "));
|
|
585
|
+
if (assertion.evidence) {
|
|
586
|
+
const limit = Math.max(240, ctx.width * 5);
|
|
587
|
+
const evidence =
|
|
588
|
+
assertion.evidence.length <= limit
|
|
589
|
+
? assertion.evidence
|
|
590
|
+
: `${assertion.evidence.slice(0, limit)}… (${assertion.evidence.length - limit} more chars; open ${item.locator} for full evidence)`;
|
|
591
|
+
lines.push(indentBlock(wrapDisplay(evidence, ctx.width - 6).join("\n"), " "));
|
|
592
|
+
}
|
|
354
593
|
}
|
|
355
594
|
return lines.join("\n");
|
|
356
595
|
}
|
|
596
|
+
|
|
597
|
+
export function attemptListText(items: AttemptListItem[], total: number | undefined, ctx: TextContext): string {
|
|
598
|
+
const locale = ctx.locale;
|
|
599
|
+
if (items.length === 0) return localeText(locale, "attemptList.empty");
|
|
600
|
+
const blocks = items.map((item) => attemptListItemText(item, ctx, locale));
|
|
601
|
+
const remaining = (total ?? items.length) - items.length;
|
|
602
|
+
if (remaining > 0) blocks.push(localeText(locale, "attemptList.truncatedText", { n: remaining }));
|
|
603
|
+
return blocks.join("\n\n");
|
|
604
|
+
}
|