niceeval 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.zh.md +11 -0
- package/docs/README.md +2 -1
- package/docs/adapters/collection.md +1 -1
- package/docs/adapters/contract.md +6 -5
- package/docs/adapters/reference/claude-code-otel-telemetry.md +1 -1
- package/docs/assertions.md +0 -1
- package/docs/capabilities-by-construction.md +2 -2
- package/docs/cli.md +2 -2
- package/docs/concepts.md +2 -2
- package/docs/e2e-ci.md +135 -98
- package/docs/eval-authoring.md +3 -3
- package/docs/experiments.md +7 -4
- package/docs/observability.md +9 -5
- package/docs/origin-integration.md +18 -18
- package/docs/references.md +1 -1
- package/docs/reports.md +551 -0
- package/docs/results-format.md +3 -3
- package/docs/results-lib.md +191 -0
- package/docs/runner.md +1 -1
- package/docs/sandbox.md +1 -1
- package/docs/source-map.md +22 -2
- package/docs/tier-sync.md +74 -58
- package/docs/view.md +39 -5
- package/package.json +29 -3
- package/src/agents/ai-sdk.ts +3 -0
- package/src/agents/claude-code.ts +18 -1
- package/src/agents/codex.ts +3 -2
- package/src/agents/index.ts +16 -0
- package/src/agents/openai-compat.test.ts +56 -0
- package/src/agents/openai-compat.ts +151 -0
- package/src/agents/types.ts +3 -1
- package/src/cli.ts +3 -2
- package/src/context/context.test.ts +78 -1
- package/src/context/context.ts +19 -11
- package/src/context/session.ts +2 -0
- package/src/context/types.ts +11 -5
- package/src/i18n/en.ts +3 -4
- package/src/i18n/zh-CN.ts +2 -3
- package/src/o11y/cost.test.ts +40 -0
- package/src/o11y/cost.ts +27 -5
- package/src/o11y/derive.ts +2 -2
- package/src/o11y/otlp/mappers/claude-code.test.ts +31 -0
- package/src/o11y/otlp/mappers/claude-code.ts +24 -0
- package/src/o11y/otlp/mappers/index.ts +1 -0
- package/src/o11y/otlp/parse.test.ts +127 -0
- package/src/o11y/prices.json +176 -119
- package/src/o11y/types.ts +2 -1
- package/src/report/aggregate.ts +215 -0
- package/src/report/compute.ts +405 -0
- package/src/report/format.ts +47 -0
- package/src/report/index.ts +40 -0
- package/src/report/metrics.ts +96 -0
- package/src/report/react/CaseList.tsx +70 -0
- package/src/report/react/DeltaTable.tsx +79 -0
- package/src/report/react/MetricMatrix.tsx +75 -0
- package/src/report/react/MetricScatter.tsx +217 -0
- package/src/report/react/MetricTable.tsx +71 -0
- package/src/report/react/RunOverview.tsx +87 -0
- package/src/report/react/Scoreboard.tsx +87 -0
- package/src/report/react/cell.tsx +49 -0
- package/src/report/react/colors.ts +42 -0
- package/src/report/react/data.ts +17 -0
- package/src/report/react/fixtures.ts +236 -0
- package/src/report/react/format.ts +34 -0
- package/src/report/react/index.tsx +34 -0
- package/src/report/react/render.test.tsx +303 -0
- package/src/report/react/styles.css +302 -0
- package/src/report/report.test.ts +667 -0
- package/src/report/types.ts +210 -0
- package/src/results/copy.ts +200 -0
- package/src/results/format.ts +75 -0
- package/src/results/index.ts +27 -0
- package/src/results/open.ts +207 -0
- package/src/results/results.test.ts +359 -0
- package/src/results/select.ts +101 -0
- package/src/results/types.ts +98 -0
- package/src/runner/attempt.ts +3 -1
- package/src/runner/report.test.ts +111 -0
- package/src/runner/report.ts +52 -1
- package/src/runner/reporters/artifacts.ts +1 -1
- package/src/runner/reporters/braintrust.test.ts +106 -0
- package/src/runner/reporters/braintrust.ts +197 -0
- package/src/runner/reporters/index.ts +3 -1
- package/src/runner/run.ts +30 -19
- package/src/runner/types.ts +17 -0
- package/src/sandbox/types.ts +3 -3
- package/src/view/app/App.tsx +82 -17
- package/src/view/app/components/CostScoreChart.tsx +127 -0
- package/src/view/app/i18n.ts +10 -1
- package/src/view/app/lib/attempt-route.test.ts +89 -0
- package/src/view/app/lib/attempt-route.ts +52 -0
- package/src/view/app/lib/chart.ts +72 -0
- package/src/view/app/lib/outcome.ts +1 -1
- package/src/view/app/types.ts +4 -5
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +18 -18
- package/src/view/index.ts +12 -18
- package/src/view/loader.ts +14 -7
- package/src/view/shared/types.ts +14 -1
- package/src/view/styles.css +43 -0
- package/src/view/template.html +0 -1
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
// 「不 hydrate 也完整」的验收测试:每个组件过 renderToStaticMarkup,
|
|
2
|
+
// 断言纯静态 HTML 里就有全部关键内容——数字、覆盖率角标、缺数据文案、
|
|
3
|
+
// 散点的 SVG 与系列名、truncated 行、attemptHref 链接。
|
|
4
|
+
// 另外锁两条契约:源码零 hooks;维度键跨组件同色。
|
|
5
|
+
|
|
6
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
10
|
+
import { describe, expect, it } from "vitest";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
CaseList,
|
|
14
|
+
DeltaTable,
|
|
15
|
+
MetricMatrix,
|
|
16
|
+
MetricScatter,
|
|
17
|
+
MetricTable,
|
|
18
|
+
RunOverview,
|
|
19
|
+
Scoreboard,
|
|
20
|
+
} from "./index.tsx";
|
|
21
|
+
import { NRE_PALETTE, colorClassForKey, colorIndexForKey } from "./colors.ts";
|
|
22
|
+
import {
|
|
23
|
+
caseListData,
|
|
24
|
+
deltaData,
|
|
25
|
+
matrixData,
|
|
26
|
+
overviewData,
|
|
27
|
+
overviewWithCost,
|
|
28
|
+
scatterData,
|
|
29
|
+
scoreboardData,
|
|
30
|
+
tableData,
|
|
31
|
+
} from "./fixtures.ts";
|
|
32
|
+
|
|
33
|
+
const attemptHref = (ref: { run: string; result: number }) => `/attempts/${ref.run}/${ref.result}`;
|
|
34
|
+
|
|
35
|
+
describe("RunOverview", () => {
|
|
36
|
+
const html = renderToStaticMarkup(<RunOverview data={overviewData} />);
|
|
37
|
+
|
|
38
|
+
it("KPI 条:快照数、题数、attempts、通过率、耗时", () => {
|
|
39
|
+
expect(html).toContain("nre-overview");
|
|
40
|
+
expect(html).toContain("<dd>12</dd>"); // 题目
|
|
41
|
+
expect(html).toContain("<dd>48</dd>"); // attempts
|
|
42
|
+
expect(html).toContain("78%"); // 36 / (36+8+2),skipped 不进分母
|
|
43
|
+
expect(html).toContain("4m 21s"); // 261000ms
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("costUSD 全缺 = null:显示缺数据,不编 $0", () => {
|
|
47
|
+
expect(html).toContain("no data");
|
|
48
|
+
expect(html).not.toContain("$0");
|
|
49
|
+
const withCost = renderToStaticMarkup(<RunOverview data={overviewWithCost} />);
|
|
50
|
+
expect(withCost).toContain("$1.23");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("数据来源与 warnings 直接渲染在条内", () => {
|
|
54
|
+
expect(html).toContain("2 snapshots");
|
|
55
|
+
expect(html).toContain("compare/bub");
|
|
56
|
+
expect(html).toContain("2026-07-01T10:00:00Z");
|
|
57
|
+
expect(html).toContain("compare/bub 快照缺 3 个 eval 的结果");
|
|
58
|
+
expect(html).toContain("nre-warnings");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("MetricTable", () => {
|
|
63
|
+
const html = renderToStaticMarkup(<MetricTable data={tableData} attemptHref={attemptHref} />);
|
|
64
|
+
|
|
65
|
+
it("按传入顺序渲染行,不重排(排序在数据侧)", () => {
|
|
66
|
+
// fixture 里 codex(50%)在 bub(87%)前面,输出必须保持
|
|
67
|
+
expect(html.indexOf(">codex<")).toBeGreaterThan(-1);
|
|
68
|
+
expect(html.indexOf(">codex<")).toBeLessThan(html.indexOf(">bub<"));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("列头带 label、unit 与 better 方向", () => {
|
|
72
|
+
expect(html).toContain("通过率");
|
|
73
|
+
expect(html).toContain("(%)");
|
|
74
|
+
expect(html).toContain("代码行数");
|
|
75
|
+
expect(html).toContain("↑");
|
|
76
|
+
expect(html).toContain("↓");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("数字与覆盖率角标:samples < total 如实标出", () => {
|
|
80
|
+
expect(html).toContain("87%");
|
|
81
|
+
expect(html).toContain("120 lines");
|
|
82
|
+
expect(html).toMatch(/<sup class="nre-coverage"[^>]*>5\/6<\/sup>/);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("全 null 渲染缺数据文案,绝不画 0", () => {
|
|
86
|
+
expect(html).toContain("no data");
|
|
87
|
+
expect(html).toContain("nre-cell-missing");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("refs + attemptHref 出普通 <a>", () => {
|
|
91
|
+
expect(html).toContain('href="/attempts/run-a/0"');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("MetricMatrix", () => {
|
|
96
|
+
const html = renderToStaticMarkup(<MetricMatrix data={matrixData} attemptHref={attemptHref} />);
|
|
97
|
+
|
|
98
|
+
it("caption 标出指标与行列维度", () => {
|
|
99
|
+
expect(html).toContain("通过率");
|
|
100
|
+
expect(html).toContain("eval × agent");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("稀疏格子:没有样本的格子空着(恰好一个)", () => {
|
|
104
|
+
expect(html.match(/nre-td-empty/g)).toHaveLength(1);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("格子数字与 refs 下钻链接", () => {
|
|
108
|
+
expect(html).toContain("100%");
|
|
109
|
+
expect(html).toContain("0%");
|
|
110
|
+
expect(html).toContain('href="/attempts/run-b/3"');
|
|
111
|
+
expect(html).toContain('href="/attempts/run-b/7"');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("列头(维度键)带稳定散列配色 class", () => {
|
|
115
|
+
expect(html).toContain(colorClassForKey("bub"));
|
|
116
|
+
expect(html).toContain(colorClassForKey("codex"));
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe("Scoreboard", () => {
|
|
121
|
+
const html = renderToStaticMarkup(<Scoreboard data={scoreboardData} />);
|
|
122
|
+
|
|
123
|
+
it("总分 + 满分口径", () => {
|
|
124
|
+
expect(html).toContain("78.5");
|
|
125
|
+
expect(html).toContain("52");
|
|
126
|
+
expect(html).toContain("/ 100");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("分科小计 earned/possible", () => {
|
|
130
|
+
expect(html).toContain("14/16");
|
|
131
|
+
expect(html).toContain("3/4");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("missing 注脚:没跑按 0 计的题数如实展示", () => {
|
|
135
|
+
expect(html).toContain("1 eval missing, scored 0");
|
|
136
|
+
expect(html).toContain("2 evals missing, scored 0");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("实际生效的权重表可审计", () => {
|
|
140
|
+
expect(html).toContain("algebra/ ×2");
|
|
141
|
+
expect(html).toContain("others ×1");
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe("MetricScatter", () => {
|
|
146
|
+
const html = renderToStaticMarkup(<MetricScatter data={scatterData} pointHref={(row) => `/exp/${row.key}`} />);
|
|
147
|
+
|
|
148
|
+
const cxOf = (key: string): number => {
|
|
149
|
+
const m = html.match(new RegExp(`data-key="${key}"[^>]*\\bcx="([\\d.]+)"`));
|
|
150
|
+
expect(m, `circle for ${key}`).toBeTruthy();
|
|
151
|
+
return Number(m![1]);
|
|
152
|
+
};
|
|
153
|
+
const cyOf = (key: string): number => {
|
|
154
|
+
const m = html.match(new RegExp(`data-key="${key}"[^>]*\\bcy="([\\d.]+)"`));
|
|
155
|
+
expect(m, `circle for ${key}`).toBeTruthy();
|
|
156
|
+
return Number(m![1]);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
it("内联 SVG + 轴标签", () => {
|
|
160
|
+
expect(html).toContain("<svg");
|
|
161
|
+
expect(html).toContain("成本($)");
|
|
162
|
+
expect(html).toContain("通过率(%)");
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("better:lower 的 x 轴反向:便宜($5)在贵($10)右边,好的角落恒在右上", () => {
|
|
166
|
+
expect(cxOf("compare/bub-low")).toBeGreaterThan(cxOf("compare/bub-high"));
|
|
167
|
+
// y 轴 better:higher:通过率高(90%)在低(50%)上方(SVG y 向下增长)
|
|
168
|
+
expect(cyOf("compare/bub-high")).toBeLessThan(cyOf("compare/bub-low"));
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("同系列点连线并标系列名,线色来自稳定散列调色板", () => {
|
|
172
|
+
expect(html).toContain("<polyline");
|
|
173
|
+
expect(html).toContain(">bub</text>");
|
|
174
|
+
expect(html).toContain(NRE_PALETTE[colorIndexForKey("bub")]);
|
|
175
|
+
// codex 只有 1 个可画点:不出线,但系列名仍标出
|
|
176
|
+
expect(html).toContain(">codex</text>");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("null 点不画,底部注脚如实报数", () => {
|
|
180
|
+
expect(html).not.toContain('data-key="compare/codex-broken"');
|
|
181
|
+
expect(html).toContain("1 point missing data");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("hover 退化为 <title>:display 与 samples/total", () => {
|
|
185
|
+
expect(html).toContain("<title>");
|
|
186
|
+
expect(html).toContain("50%(6/6)");
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("pointHref:点包普通 <a>", () => {
|
|
190
|
+
expect(html).toContain('href="/exp/compare/bub-low"');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("全部点都缺数据时不画空坐标系", () => {
|
|
194
|
+
const empty = renderToStaticMarkup(
|
|
195
|
+
<MetricScatter
|
|
196
|
+
data={{
|
|
197
|
+
...scatterData,
|
|
198
|
+
rows: scatterData.rows.map((r) => ({ ...r, x: { ...r.x, value: null } })),
|
|
199
|
+
}}
|
|
200
|
+
/>,
|
|
201
|
+
);
|
|
202
|
+
expect(empty).not.toContain("<svg");
|
|
203
|
+
expect(empty).toContain("no data");
|
|
204
|
+
expect(empty).toContain("4 points missing data");
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe("DeltaTable", () => {
|
|
209
|
+
const html = renderToStaticMarkup(<DeltaTable data={deltaData} />);
|
|
210
|
+
|
|
211
|
+
it("每格 A/B/Δ 三值", () => {
|
|
212
|
+
expect(html).toContain("50%");
|
|
213
|
+
expect(html).toContain("62%");
|
|
214
|
+
expect(html).toContain("+12pp");
|
|
215
|
+
expect(html).toContain(">A</span>");
|
|
216
|
+
expect(html).toContain(">B</span>");
|
|
217
|
+
expect(html).toContain(">Δ</span>");
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("涨跌好坏按 better 配色:通过率涨=好,成本涨=坏,0=平", () => {
|
|
221
|
+
expect(html).toContain("nre-delta-good");
|
|
222
|
+
expect(html).toContain("nre-delta-bad");
|
|
223
|
+
expect(html).toContain("nre-delta-flat");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("任一侧缺数据:Δ 显示为缺,不硬算", () => {
|
|
227
|
+
expect(html).toContain("nre-delta-missing");
|
|
228
|
+
expect(html).toContain("no data");
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("每行标出 A → B 的 experimentId", () => {
|
|
232
|
+
expect(html).toContain("compare/bub → compare/bub--agents-md");
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe("CaseList", () => {
|
|
237
|
+
const html = renderToStaticMarkup(<CaseList data={caseListData} attemptHref={attemptHref} />);
|
|
238
|
+
|
|
239
|
+
it("逐条失败断言:name、score、detail、evidence", () => {
|
|
240
|
+
expect(html).toContain("roots-correct");
|
|
241
|
+
expect(html).toContain("score 0");
|
|
242
|
+
expect(html).toContain("期望 x=2,得到 x=3");
|
|
243
|
+
expect(html).toContain("judge: 求根公式代入时符号写反");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("errored 的 error 摘要", () => {
|
|
247
|
+
expect(html).toContain("TypeError: cannot read properties of undefined");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("truncated 如实报「还有 n 条没列」", () => {
|
|
251
|
+
expect(html).toContain("and 2 more not shown");
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("每条案例带 attemptHref 下钻链接", () => {
|
|
255
|
+
expect(html).toContain('href="/attempts/run-a/4"');
|
|
256
|
+
expect(html).toContain('href="/attempts/run-c/1"');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("长文本收进 <details>,零 JS 可展开", () => {
|
|
260
|
+
expect(html).toContain("<details");
|
|
261
|
+
expect(html).toContain("<summary>");
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
describe("跨组件契约", () => {
|
|
266
|
+
it("同一维度键在所有块里同色(稳定散列,与渲染顺序无关)", () => {
|
|
267
|
+
const cls = colorClassForKey("bub");
|
|
268
|
+
const table = renderToStaticMarkup(<MetricTable data={tableData} />);
|
|
269
|
+
const matrix = renderToStaticMarkup(<MetricMatrix data={matrixData} />);
|
|
270
|
+
const board = renderToStaticMarkup(<Scoreboard data={scoreboardData} />);
|
|
271
|
+
const delta = renderToStaticMarkup(<DeltaTable data={deltaData} />);
|
|
272
|
+
const cases = renderToStaticMarkup(<CaseList data={caseListData} />);
|
|
273
|
+
for (const html of [table, matrix, board, delta, cases]) {
|
|
274
|
+
expect(html).toContain(cls);
|
|
275
|
+
}
|
|
276
|
+
const scatter = renderToStaticMarkup(<MetricScatter data={scatterData} />);
|
|
277
|
+
expect(scatter).toContain(NRE_PALETTE[colorIndexForKey("bub")]);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("静态输出不含 <script>:交互只靠 <a>/<details>/CSS", () => {
|
|
281
|
+
const all = [
|
|
282
|
+
renderToStaticMarkup(<RunOverview data={overviewData} />),
|
|
283
|
+
renderToStaticMarkup(<MetricTable data={tableData} attemptHref={attemptHref} />),
|
|
284
|
+
renderToStaticMarkup(<MetricMatrix data={matrixData} attemptHref={attemptHref} />),
|
|
285
|
+
renderToStaticMarkup(<Scoreboard data={scoreboardData} />),
|
|
286
|
+
renderToStaticMarkup(<MetricScatter data={scatterData} />),
|
|
287
|
+
renderToStaticMarkup(<DeltaTable data={deltaData} />),
|
|
288
|
+
renderToStaticMarkup(<CaseList data={caseListData} />),
|
|
289
|
+
].join("");
|
|
290
|
+
expect(all).not.toContain("<script");
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("组件源码零 hooks(本实验的「不 hydrate 也完整」用最笨的方式保证)", () => {
|
|
294
|
+
const dir = dirname(fileURLToPath(import.meta.url));
|
|
295
|
+
const sources = readdirSync(dir)
|
|
296
|
+
.filter((f) => (f.endsWith(".tsx") || f.endsWith(".ts")) && !f.includes(".test."))
|
|
297
|
+
.map((f) => readFileSync(join(dir, f), "utf8"));
|
|
298
|
+
expect(sources.length).toBeGreaterThanOrEqual(12);
|
|
299
|
+
for (const src of sources) {
|
|
300
|
+
expect(src).not.toMatch(/\buse[A-Z][A-Za-z]*\s*\(/);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
});
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* niceeval/report/react/styles.css —— 随包发布的默认样式。
|
|
3
|
+
* 契约(docs/reports.md「四条跨组件契约」):
|
|
4
|
+
* - 所有节点都有稳定的 nre-* 类名;这份 CSS + 类名 = 静态页零依赖成立;
|
|
5
|
+
* - 要定制,在本文件之后加载自己的覆盖即可,不需要构建工具;
|
|
6
|
+
* - 调色板 --nre-c0..c7 的值与 src/report/react/colors.ts 的 NRE_PALETTE
|
|
7
|
+
* 逐个对应(SVG 内联 hex 用那份),改色时两边一起改。
|
|
8
|
+
* 视觉刻意极简:结构与诚实细节优先,品牌化交给使用者的覆盖样式。
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
.nre {
|
|
12
|
+
/* 调色板:键 → 稳定散列下标 → 同键同色(colors.ts) */
|
|
13
|
+
--nre-c0: #2563eb;
|
|
14
|
+
--nre-c1: #db2777;
|
|
15
|
+
--nre-c2: #059669;
|
|
16
|
+
--nre-c3: #d97706;
|
|
17
|
+
--nre-c4: #7c3aed;
|
|
18
|
+
--nre-c5: #0891b2;
|
|
19
|
+
--nre-c6: #dc2626;
|
|
20
|
+
--nre-c7: #65a30d;
|
|
21
|
+
|
|
22
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
23
|
+
font-size: 14px;
|
|
24
|
+
line-height: 1.5;
|
|
25
|
+
color: #171717;
|
|
26
|
+
margin: 1rem 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* ---- 维度键上色:同一个 key 在任何块里同色 ---- */
|
|
30
|
+
.nre .nre-c0 { color: var(--nre-c0); }
|
|
31
|
+
.nre .nre-c1 { color: var(--nre-c1); }
|
|
32
|
+
.nre .nre-c2 { color: var(--nre-c2); }
|
|
33
|
+
.nre .nre-c3 { color: var(--nre-c3); }
|
|
34
|
+
.nre .nre-c4 { color: var(--nre-c4); }
|
|
35
|
+
.nre .nre-c5 { color: var(--nre-c5); }
|
|
36
|
+
.nre .nre-c6 { color: var(--nre-c6); }
|
|
37
|
+
.nre .nre-c7 { color: var(--nre-c7); }
|
|
38
|
+
.nre .nre-key { font-weight: 600; }
|
|
39
|
+
|
|
40
|
+
/* ---- 表格通用 ---- */
|
|
41
|
+
table.nre,
|
|
42
|
+
.nre table {
|
|
43
|
+
border-collapse: collapse;
|
|
44
|
+
width: 100%;
|
|
45
|
+
}
|
|
46
|
+
.nre th,
|
|
47
|
+
.nre td {
|
|
48
|
+
border: 1px solid #e5e5e5;
|
|
49
|
+
padding: 0.4rem 0.6rem;
|
|
50
|
+
text-align: left;
|
|
51
|
+
vertical-align: top;
|
|
52
|
+
}
|
|
53
|
+
.nre thead th {
|
|
54
|
+
background: #fafafa;
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
}
|
|
57
|
+
.nre .nre-dimension {
|
|
58
|
+
color: #737373;
|
|
59
|
+
font-weight: 500;
|
|
60
|
+
}
|
|
61
|
+
.nre .nre-unit {
|
|
62
|
+
color: #a3a3a3;
|
|
63
|
+
font-weight: 400;
|
|
64
|
+
margin-left: 0.25em;
|
|
65
|
+
}
|
|
66
|
+
.nre .nre-better {
|
|
67
|
+
color: #a3a3a3;
|
|
68
|
+
margin-left: 0.25em;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* ---- MetricCell:缺数据 / 覆盖率角标 / 证据链接 ---- */
|
|
72
|
+
.nre .nre-missing {
|
|
73
|
+
color: #a3a3a3;
|
|
74
|
+
font-style: italic;
|
|
75
|
+
}
|
|
76
|
+
.nre .nre-coverage {
|
|
77
|
+
color: #b45309;
|
|
78
|
+
font-size: 0.72em;
|
|
79
|
+
margin-left: 0.3em;
|
|
80
|
+
}
|
|
81
|
+
.nre .nre-refs {
|
|
82
|
+
margin-left: 0.4em;
|
|
83
|
+
}
|
|
84
|
+
.nre .nre-ref {
|
|
85
|
+
color: #2563eb;
|
|
86
|
+
font-size: 0.85em;
|
|
87
|
+
text-decoration: none;
|
|
88
|
+
margin-right: 0.25em;
|
|
89
|
+
}
|
|
90
|
+
.nre .nre-ref:hover {
|
|
91
|
+
text-decoration: underline;
|
|
92
|
+
}
|
|
93
|
+
.nre .nre-td-empty {
|
|
94
|
+
background: #fafafa;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* ---- RunOverview ---- */
|
|
98
|
+
.nre-overview .nre-kpis {
|
|
99
|
+
display: flex;
|
|
100
|
+
flex-wrap: wrap;
|
|
101
|
+
gap: 1.5rem;
|
|
102
|
+
margin: 0 0 0.5rem;
|
|
103
|
+
}
|
|
104
|
+
.nre-overview .nre-kpi dt {
|
|
105
|
+
color: #737373;
|
|
106
|
+
font-size: 0.85em;
|
|
107
|
+
}
|
|
108
|
+
.nre-overview .nre-kpi dd {
|
|
109
|
+
margin: 0;
|
|
110
|
+
font-size: 1.4em;
|
|
111
|
+
font-weight: 600;
|
|
112
|
+
}
|
|
113
|
+
.nre-overview .nre-outcomes {
|
|
114
|
+
margin: 0.25rem 0;
|
|
115
|
+
color: #525252;
|
|
116
|
+
}
|
|
117
|
+
.nre-overview .nre-outcome {
|
|
118
|
+
margin-right: 1em;
|
|
119
|
+
}
|
|
120
|
+
.nre .nre-outcome-passed { color: #059669; }
|
|
121
|
+
.nre .nre-outcome-failed { color: #dc2626; }
|
|
122
|
+
.nre .nre-outcome-errored { color: #b45309; }
|
|
123
|
+
.nre .nre-outcome-skipped { color: #a3a3a3; }
|
|
124
|
+
.nre-overview .nre-source {
|
|
125
|
+
color: #737373;
|
|
126
|
+
font-size: 0.85em;
|
|
127
|
+
margin: 0.25rem 0;
|
|
128
|
+
}
|
|
129
|
+
.nre-overview .nre-source-snapshot {
|
|
130
|
+
margin-left: 0.75em;
|
|
131
|
+
}
|
|
132
|
+
.nre .nre-warnings {
|
|
133
|
+
list-style: none;
|
|
134
|
+
margin: 0.5rem 0 0;
|
|
135
|
+
padding: 0.5rem 0.75rem;
|
|
136
|
+
background: #fffbeb;
|
|
137
|
+
border: 1px solid #fde68a;
|
|
138
|
+
color: #92400e;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* ---- MetricMatrix ---- */
|
|
142
|
+
.nre-metric-matrix .nre-matrix-caption {
|
|
143
|
+
caption-side: top;
|
|
144
|
+
text-align: left;
|
|
145
|
+
font-weight: 600;
|
|
146
|
+
padding-bottom: 0.4rem;
|
|
147
|
+
}
|
|
148
|
+
.nre-metric-matrix .nre-matrix-axes {
|
|
149
|
+
color: #a3a3a3;
|
|
150
|
+
font-weight: 400;
|
|
151
|
+
margin-left: 0.75em;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* ---- Scoreboard ---- */
|
|
155
|
+
.nre-scoreboard .nre-total {
|
|
156
|
+
font-weight: 700;
|
|
157
|
+
}
|
|
158
|
+
.nre-scoreboard .nre-full-marks {
|
|
159
|
+
color: #a3a3a3;
|
|
160
|
+
font-weight: 400;
|
|
161
|
+
margin-left: 0.25em;
|
|
162
|
+
}
|
|
163
|
+
.nre-scoreboard .nre-subject-missing {
|
|
164
|
+
display: block;
|
|
165
|
+
color: #b45309;
|
|
166
|
+
font-size: 0.8em;
|
|
167
|
+
}
|
|
168
|
+
.nre-scoreboard .nre-weights {
|
|
169
|
+
color: #737373;
|
|
170
|
+
font-size: 0.85em;
|
|
171
|
+
margin: 0.4rem 0 0;
|
|
172
|
+
}
|
|
173
|
+
.nre-scoreboard .nre-weight,
|
|
174
|
+
.nre-scoreboard .nre-weight-rest {
|
|
175
|
+
margin-left: 0.5em;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* ---- MetricScatter ---- */
|
|
179
|
+
.nre-metric-scatter {
|
|
180
|
+
margin: 1rem 0;
|
|
181
|
+
}
|
|
182
|
+
.nre-metric-scatter .nre-scatter-svg {
|
|
183
|
+
max-width: 640px;
|
|
184
|
+
width: 100%;
|
|
185
|
+
height: auto;
|
|
186
|
+
display: block;
|
|
187
|
+
}
|
|
188
|
+
.nre-metric-scatter .nre-scatter-missing {
|
|
189
|
+
color: #b45309;
|
|
190
|
+
font-size: 0.85em;
|
|
191
|
+
margin: 0.25rem 0 0;
|
|
192
|
+
}
|
|
193
|
+
.nre-metric-scatter .nre-scatter-point-link circle:hover {
|
|
194
|
+
stroke: #171717;
|
|
195
|
+
stroke-width: 1.5;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* ---- DeltaTable ---- */
|
|
199
|
+
.nre-delta-table .nre-pair-ids {
|
|
200
|
+
display: block;
|
|
201
|
+
color: #a3a3a3;
|
|
202
|
+
font-size: 0.8em;
|
|
203
|
+
font-weight: 400;
|
|
204
|
+
}
|
|
205
|
+
.nre-delta-table .nre-delta-side {
|
|
206
|
+
display: block;
|
|
207
|
+
white-space: nowrap;
|
|
208
|
+
}
|
|
209
|
+
.nre-delta-table .nre-delta-tag {
|
|
210
|
+
display: inline-block;
|
|
211
|
+
width: 1.2em;
|
|
212
|
+
color: #a3a3a3;
|
|
213
|
+
font-size: 0.8em;
|
|
214
|
+
}
|
|
215
|
+
.nre .nre-delta-good { color: #059669; }
|
|
216
|
+
.nre .nre-delta-bad { color: #dc2626; }
|
|
217
|
+
.nre .nre-delta-flat { color: #737373; }
|
|
218
|
+
.nre .nre-delta-neutral { color: #171717; }
|
|
219
|
+
.nre .nre-delta-missing { color: #a3a3a3; }
|
|
220
|
+
|
|
221
|
+
/* ---- CaseList ---- */
|
|
222
|
+
.nre-case-list .nre-cases {
|
|
223
|
+
list-style: none;
|
|
224
|
+
margin: 0;
|
|
225
|
+
padding: 0;
|
|
226
|
+
}
|
|
227
|
+
.nre-case-list .nre-case {
|
|
228
|
+
border: 1px solid #e5e5e5;
|
|
229
|
+
border-left-width: 3px;
|
|
230
|
+
padding: 0.6rem 0.75rem;
|
|
231
|
+
margin-bottom: 0.5rem;
|
|
232
|
+
}
|
|
233
|
+
.nre-case-list .nre-case-failed { border-left-color: #dc2626; }
|
|
234
|
+
.nre-case-list .nre-case-errored { border-left-color: #b45309; }
|
|
235
|
+
.nre-case-list .nre-case-head {
|
|
236
|
+
display: flex;
|
|
237
|
+
flex-wrap: wrap;
|
|
238
|
+
gap: 0.75em;
|
|
239
|
+
align-items: baseline;
|
|
240
|
+
}
|
|
241
|
+
.nre-case-list .nre-case-outcome {
|
|
242
|
+
font-weight: 700;
|
|
243
|
+
text-transform: uppercase;
|
|
244
|
+
font-size: 0.8em;
|
|
245
|
+
}
|
|
246
|
+
.nre-case-list .nre-case-eval {
|
|
247
|
+
font-weight: 600;
|
|
248
|
+
}
|
|
249
|
+
.nre-case-list .nre-case-experiment,
|
|
250
|
+
.nre-case-list .nre-case-duration,
|
|
251
|
+
.nre-case-list .nre-case-cost {
|
|
252
|
+
color: #737373;
|
|
253
|
+
font-size: 0.9em;
|
|
254
|
+
}
|
|
255
|
+
.nre-case-list .nre-case-link {
|
|
256
|
+
margin-left: auto;
|
|
257
|
+
color: #2563eb;
|
|
258
|
+
}
|
|
259
|
+
.nre-case-list .nre-case-error {
|
|
260
|
+
margin: 0.4rem 0 0;
|
|
261
|
+
padding: 0.3rem 0.5rem;
|
|
262
|
+
background: #fef2f2;
|
|
263
|
+
color: #991b1b;
|
|
264
|
+
font-family: ui-monospace, monospace;
|
|
265
|
+
font-size: 0.85em;
|
|
266
|
+
white-space: pre-wrap;
|
|
267
|
+
}
|
|
268
|
+
.nre-case-list .nre-assertions {
|
|
269
|
+
margin: 0.4rem 0 0;
|
|
270
|
+
padding-left: 1.25rem;
|
|
271
|
+
}
|
|
272
|
+
.nre-case-list .nre-assertion-name {
|
|
273
|
+
font-family: ui-monospace, monospace;
|
|
274
|
+
}
|
|
275
|
+
.nre-case-list .nre-assertion-score {
|
|
276
|
+
color: #737373;
|
|
277
|
+
margin-left: 0.75em;
|
|
278
|
+
font-size: 0.9em;
|
|
279
|
+
}
|
|
280
|
+
.nre-case-list .nre-assertion-more summary {
|
|
281
|
+
cursor: pointer;
|
|
282
|
+
color: #737373;
|
|
283
|
+
font-size: 0.85em;
|
|
284
|
+
}
|
|
285
|
+
.nre-case-list .nre-assertion-detail {
|
|
286
|
+
margin: 0.25rem 0;
|
|
287
|
+
white-space: pre-wrap;
|
|
288
|
+
}
|
|
289
|
+
.nre-case-list .nre-assertion-evidence {
|
|
290
|
+
margin: 0.25rem 0 0.25rem 0.75rem;
|
|
291
|
+
padding-left: 0.5rem;
|
|
292
|
+
border-left: 2px solid #d4d4d4;
|
|
293
|
+
color: #525252;
|
|
294
|
+
white-space: pre-wrap;
|
|
295
|
+
}
|
|
296
|
+
.nre-case-list .nre-truncated {
|
|
297
|
+
color: #b45309;
|
|
298
|
+
font-size: 0.9em;
|
|
299
|
+
}
|
|
300
|
+
.nre-case-list .nre-case-empty {
|
|
301
|
+
color: #737373;
|
|
302
|
+
}
|