niceeval 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INDEX.md +77 -45
- package/dist/agents/types.d.ts +28 -6
- package/dist/i18n/en.d.ts +2 -0
- package/dist/i18n/zh-CN.d.ts +2 -0
- package/dist/report/built-in/index.d.ts +3 -2
- package/dist/report/built-in/index.js +7 -8
- package/dist/report/built-in/standard.d.ts +1 -0
- package/dist/report/built-in/standard.js +30 -0
- package/dist/report/components.d.ts +69 -2
- package/dist/report/components.js +152 -3
- package/dist/report/compute.d.ts +28 -1
- package/dist/report/compute.js +123 -0
- package/dist/report/index.d.ts +4 -4
- package/dist/report/index.js +3 -2
- package/dist/report/locale.d.ts +39 -1
- package/dist/report/locale.js +69 -0
- package/dist/report/react/AttemptList.d.ts +3 -1
- package/dist/report/react/AttemptList.js +3 -3
- package/dist/report/react/CopyFixPrompt.d.ts +12 -0
- package/dist/report/react/CopyFixPrompt.js +12 -0
- package/dist/report/react/HeroCard.d.ts +13 -0
- package/dist/report/react/HeroCard.js +35 -0
- package/dist/report/react/PoweredBy.d.ts +5 -0
- package/dist/report/react/PoweredBy.js +7 -0
- package/dist/report/react/ScopeWarnings.d.ts +12 -0
- package/dist/report/react/ScopeWarnings.js +18 -0
- package/dist/report/react/TraceWaterfall.d.ts +14 -0
- package/dist/report/react/TraceWaterfall.js +22 -0
- package/dist/report/react/index.d.ts +6 -1
- package/dist/report/react/index.js +6 -0
- package/dist/report/report.d.ts +22 -6
- package/dist/report/report.js +66 -53
- package/dist/report/scope-warnings.d.ts +28 -0
- package/dist/report/scope-warnings.js +101 -0
- package/dist/report/text/faces.d.ts +19 -1
- package/dist/report/text/faces.js +61 -0
- package/dist/report/tree.js +6 -1
- package/dist/report/types.d.ts +45 -0
- package/dist/report/web.d.ts +5 -4
- package/dist/report/web.js +7 -22
- package/dist/results/select.d.ts +15 -2
- package/dist/results/select.js +75 -10
- package/dist/results/types.d.ts +26 -0
- package/dist/runner/fingerprint.d.ts +3 -3
- package/dist/runner/sandbox-selection.d.ts +12 -0
- package/dist/runner/types.d.ts +18 -6
- package/dist/sandbox/types.d.ts +12 -0
- package/docs-site/zh/explanation/evals.mdx +2 -1
- package/docs-site/zh/explanation/experiment.mdx +2 -0
- package/docs-site/zh/how-to/custom-reports.mdx +22 -6
- package/docs-site/zh/how-to/publish-report.mdx +5 -4
- package/docs-site/zh/how-to/viewing-results.mdx +3 -3
- package/docs-site/zh/how-to/write-experiment.mdx +40 -1
- package/docs-site/zh/reference/builtin-agents.mdx +40 -3
- package/docs-site/zh/reference/cli.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +8 -0
- package/docs-site/zh/reference/official-adapters.mdx +10 -5
- package/package.json +2 -1
- package/src/agents/bub.ts +13 -1
- package/src/agents/claude-code.test.ts +43 -1
- package/src/agents/claude-code.ts +32 -14
- package/src/agents/codex.test.ts +168 -1
- package/src/agents/codex.ts +51 -15
- package/src/agents/mcp.ts +31 -0
- package/src/agents/post-setup.ts +33 -0
- package/src/agents/types.ts +28 -7
- package/src/cli.ts +8 -6
- package/src/define.ts +3 -0
- package/src/i18n/en.ts +5 -2
- package/src/i18n/zh-CN.ts +5 -1
- package/src/index.ts +1 -0
- package/src/report/built-in/index.tsx +8 -7
- package/src/report/built-in/standard.tsx +59 -0
- package/src/report/components.tsx +218 -2
- package/src/report/compute.ts +138 -1
- package/src/report/dual-render.test.tsx +139 -12
- package/src/report/index.ts +20 -0
- package/src/report/locale.ts +83 -1
- package/src/report/react/AttemptList.tsx +13 -1
- package/src/report/react/CopyFixPrompt.tsx +37 -0
- package/src/report/react/HeroCard.tsx +59 -0
- package/src/report/react/PoweredBy.tsx +20 -0
- package/src/report/react/ScopeWarnings.tsx +74 -0
- package/src/report/react/TraceWaterfall.tsx +78 -0
- package/src/report/react/enhance.js +14 -0
- package/src/report/react/index.tsx +11 -0
- package/src/report/react/styles.css +187 -7
- package/src/report/report.ts +97 -62
- package/src/report/scope-warnings.ts +155 -0
- package/src/report/site-components.test.tsx +526 -0
- package/src/report/text/faces.ts +66 -0
- package/src/report/tree.ts +8 -1
- package/src/report/types.ts +51 -0
- package/src/report/web.ts +7 -40
- package/src/results/host-equivalence.test.ts +5 -1
- package/src/results/open.ts +5 -4
- package/src/results/results.test.ts +78 -1
- package/src/results/select.ts +78 -10
- package/src/results/types.ts +27 -0
- package/src/runner/attempt.ts +10 -9
- package/src/runner/discover.test.ts +9 -1
- package/src/runner/discover.ts +3 -3
- package/src/runner/fingerprint.ts +9 -4
- package/src/runner/ledger.test.ts +30 -1
- package/src/runner/ledger.ts +26 -4
- package/src/runner/run.ts +5 -1
- package/src/runner/sandbox-selection.test.ts +131 -0
- package/src/runner/sandbox-selection.ts +110 -0
- package/src/runner/types.ts +19 -2
- package/src/sandbox/types.ts +6 -0
- package/src/show/index.ts +17 -10
- package/src/show/render.ts +11 -11
- package/src/show/report-host.test.ts +32 -15
- package/src/show/report-host.ts +5 -4
- package/src/show/show.test.ts +140 -3
- package/src/view/app/App.test.tsx +78 -17
- package/src/view/app/App.tsx +17 -78
- package/src/view/app/components/CopyControls.tsx +4 -42
- package/src/view/app/i18n.ts +5 -227
- package/src/view/app/lib/rows.ts +3 -21
- package/src/view/app/shared.ts +1 -3
- package/src/view/app/types.ts +2 -2
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +20 -20
- package/src/view/data.ts +2 -2
- package/src/view/shared/types.ts +11 -6
- package/src/view/styles.css +6 -266
- package/src/view/view-report.test.ts +59 -22
- package/src/view/app/components/LazyArtifact.tsx +0 -51
- package/src/view/app/components/SkippedRunsBanner.tsx +0 -140
- package/src/view/app/pages/AttemptsPage.tsx +0 -80
- package/src/view/app/pages/TracesPage.tsx +0 -35
package/src/view/data.ts
CHANGED
|
@@ -267,8 +267,8 @@ export async function loadViewScan(input?: string, opts: ViewScanOptions = {}):
|
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
-
// 全局最新快照(跨全部实验):hero
|
|
271
|
-
//
|
|
270
|
+
// 全局最新快照(跨全部实验):viewData.lastRunAt 从这里取。页内 hero 的「最后运行」
|
|
271
|
+
// 显示由 Hero 组件按 heroData(scope) 自己算,不吃这份字段。
|
|
272
272
|
let latestSnapshot: Snapshot | undefined;
|
|
273
273
|
for (const exp of results.experiments) {
|
|
274
274
|
const candidate = exp.snapshots[0];
|
package/src/view/shared/types.ts
CHANGED
|
@@ -33,8 +33,9 @@ export interface ViewReportPageMeta {
|
|
|
33
33
|
/**
|
|
34
34
|
* 规范化后的报告外壳声明(docs/feature/reports/library/shell.md):壳(导航 / 页脚)由前端
|
|
35
35
|
* 渲染,页内容消费 <template> 静态块。title 已走完回退链(def.title → 唯一且相同的快照 name →
|
|
36
|
-
* 内置文案「Eval 运行结果 / Eval Results」)
|
|
37
|
-
*
|
|
36
|
+
* 内置文案「Eval 运行结果 / Eval Results」),宿主落点只有浏览器 <title>(文档单例);
|
|
37
|
+
* 页内 hero 标题由 Hero 组件消费同一取值链,品牌是组件、宿主页头不渲染任何品牌位。
|
|
38
|
+
* scripts / styles 是注入资产,不进 viewData。
|
|
38
39
|
* link 的 icon 是内联 SVG 字符串(原样透传、原样内联),不收组件——viewData 就是序列化边界。
|
|
39
40
|
*/
|
|
40
41
|
export interface ViewReportMeta {
|
|
@@ -70,7 +71,11 @@ export interface ViewSnapshot {
|
|
|
70
71
|
results: ViewEvalResult[];
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* 目录扫描里被跳过的 run 的结构化条目;三种原因与 niceeval/results 的 skipped 一致。
|
|
76
|
+
* 页面上的呈现不走它:不可读快照已形成 `unreadable-snapshot` Scope warning,由报告页内的
|
|
77
|
+
* `ScopeWarnings` 组件显示;这里只随 viewData 携带原始事实。
|
|
78
|
+
*/
|
|
74
79
|
export interface SkippedRunNotice {
|
|
75
80
|
/** run 目录,相对 cwd。 */
|
|
76
81
|
dir: string;
|
|
@@ -92,11 +97,11 @@ export interface SkippedRunNotice {
|
|
|
92
97
|
export interface ViewData {
|
|
93
98
|
/** 最近一次 run 的 startedAt(ISO);没有历史 run 时缺省。 */
|
|
94
99
|
lastRunAt?: string;
|
|
95
|
-
/** 报告槽 Selection 合成自几个物理 run
|
|
100
|
+
/** 报告槽 Selection 合成自几个物理 run。 */
|
|
96
101
|
composedRuns: number;
|
|
97
|
-
/** 全部历史快照(跨快照按身份键去重后)
|
|
102
|
+
/** 全部历史快照(跨快照按身份键去重后);attempt 详情路由对这份完整集合解析,不随报告 Scope 收窄。 */
|
|
98
103
|
snapshots: ViewSnapshot[];
|
|
99
|
-
/** 读不了的落盘(三种原因)
|
|
104
|
+
/** 读不了的落盘(三种原因);呈现走报告页内的 ScopeWarnings(unreadable-snapshot warning)。 */
|
|
100
105
|
skippedRuns?: SkippedRunNotice[];
|
|
101
106
|
/** 报告外壳与页导航的声明(规范化后);缺省时前端按单页 `report` 兜底。 */
|
|
102
107
|
report?: ViewReportMeta;
|
package/src/view/styles.css
CHANGED
|
@@ -63,8 +63,8 @@ a { color: inherit; }
|
|
|
63
63
|
align-items: center;
|
|
64
64
|
justify-content: space-between;
|
|
65
65
|
/* space-between 只在有富余空间时才撑开相邻项;.shell-links 的 margin-left:auto 会把富余
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
空间全部吸收到它左侧,导致相邻项贴脸。显式 gap 保证每一对相邻项(导航—shell-links、
|
|
67
|
+
shell-links—语言切换)恒有最小间距,不依赖富余空间。 */
|
|
68
68
|
gap: 28px;
|
|
69
69
|
padding: 0 clamp(20px, 5vw, 80px);
|
|
70
70
|
background: color-mix(in oklch, var(--bg), transparent 4%);
|
|
@@ -73,20 +73,7 @@ a { color: inherit; }
|
|
|
73
73
|
z-index: 10;
|
|
74
74
|
backdrop-filter: blur(10px);
|
|
75
75
|
}
|
|
76
|
-
|
|
77
|
-
display: flex;
|
|
78
|
-
align-items: baseline;
|
|
79
|
-
gap: 12px;
|
|
80
|
-
font-weight: 690;
|
|
81
|
-
font-size: 20px;
|
|
82
|
-
}
|
|
83
|
-
.mark {
|
|
84
|
-
width: 18px;
|
|
85
|
-
height: 18px;
|
|
86
|
-
border: 1.25px solid var(--text);
|
|
87
|
-
transform: rotate(45deg);
|
|
88
|
-
display: inline-block;
|
|
89
|
-
}
|
|
76
|
+
/* 页头无品牌位:品牌是页内组件(PoweredBy / Hero 的品牌行,nre-powered-by),宿主不渲染。 */
|
|
90
77
|
/* 顶部导航 .nav / .nav-tab → components/ui/tabs.tsx(Radix Tabs + Tailwind utilities)。 */
|
|
91
78
|
.lang-switch {
|
|
92
79
|
display: inline-flex;
|
|
@@ -121,219 +108,13 @@ main {
|
|
|
121
108
|
margin: 0 auto;
|
|
122
109
|
padding: clamp(52px, 7vw, 82px) 0 72px;
|
|
123
110
|
}
|
|
124
|
-
.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
margin: 0 auto clamp(38px, 6vw, 58px);
|
|
128
|
-
}
|
|
129
|
-
h1 {
|
|
130
|
-
margin: 0;
|
|
131
|
-
font-size: clamp(42px, 5.6vw, 66px);
|
|
132
|
-
line-height: 0.94;
|
|
133
|
-
font-weight: 760;
|
|
134
|
-
letter-spacing: 0;
|
|
135
|
-
}
|
|
136
|
-
.subtitle {
|
|
137
|
-
margin: 20px auto 0;
|
|
138
|
-
color: color-mix(in oklch, var(--muted), var(--text) 12%);
|
|
139
|
-
font-size: clamp(17px, 2vw, 21px);
|
|
140
|
-
line-height: 1.58;
|
|
141
|
-
max-width: 660px;
|
|
142
|
-
}
|
|
143
|
-
.meta {
|
|
144
|
-
display: flex;
|
|
145
|
-
flex-wrap: wrap;
|
|
146
|
-
justify-content: center;
|
|
147
|
-
gap: 14px;
|
|
148
|
-
margin-top: 24px;
|
|
149
|
-
color: var(--muted);
|
|
150
|
-
font-size: 14px;
|
|
151
|
-
}
|
|
152
|
-
.meta b { color: var(--text); font-weight: 570; }
|
|
153
|
-
/* --report 报告槽:server 侧渲染好的静态报告 HTML(nre-* 类的样式随包 styles.css 注入),
|
|
154
|
-
这里只给槽位留呼吸空间,不覆盖报告自己的样式。 */
|
|
111
|
+
/* 报告槽:server 侧渲染好的静态报告 HTML(nre-* 类的样式随包 styles.css 注入),
|
|
112
|
+
这里只给槽位留呼吸空间,不覆盖报告自己的样式。hero、警告区、批量修复 prompt 都是
|
|
113
|
+
页内组件(nre-hero / nre-warnings / nre-copy-fix-prompt),宿主不再有对应 chrome。 */
|
|
155
114
|
.report-slot {
|
|
156
115
|
margin: 0 auto 36px;
|
|
157
116
|
}
|
|
158
|
-
/* schemaVersion 不匹配等读不了的 run:报告槽不解析它们,这里占位提示。同一原因(且文案
|
|
159
|
-
完全相同)的条目按 SkippedRunsBanner 分组折叠,不逐条重复整句原因 + 命令——那是原实现
|
|
160
|
-
把首屏挤满、警示色又淡到看不出来的根因。 */
|
|
161
|
-
.incompatible-banner {
|
|
162
|
-
border: 1px solid color-mix(in oklch, var(--warn), var(--line) 35%);
|
|
163
|
-
border-left: 3px solid var(--warn);
|
|
164
|
-
border-radius: 8px;
|
|
165
|
-
background: color-mix(in oklch, var(--warn), var(--panel) 85%);
|
|
166
|
-
padding: 12px 16px 14px;
|
|
167
|
-
margin: 0 auto 36px;
|
|
168
|
-
font-size: 13px;
|
|
169
|
-
}
|
|
170
|
-
.ib-head {
|
|
171
|
-
display: flex;
|
|
172
|
-
align-items: center;
|
|
173
|
-
gap: 8px;
|
|
174
|
-
}
|
|
175
|
-
.ib-head b { font-weight: 640; }
|
|
176
|
-
.ib-icon {
|
|
177
|
-
width: 16px;
|
|
178
|
-
height: 16px;
|
|
179
|
-
flex-shrink: 0;
|
|
180
|
-
color: var(--warn);
|
|
181
|
-
stroke-width: 2;
|
|
182
|
-
}
|
|
183
|
-
.ib-groups {
|
|
184
|
-
display: grid;
|
|
185
|
-
gap: 10px;
|
|
186
|
-
margin-top: 10px;
|
|
187
|
-
}
|
|
188
|
-
.ib-group-head {
|
|
189
|
-
display: flex;
|
|
190
|
-
align-items: baseline;
|
|
191
|
-
gap: 8px;
|
|
192
|
-
}
|
|
193
|
-
.ib-group-count {
|
|
194
|
-
display: inline-flex;
|
|
195
|
-
align-items: center;
|
|
196
|
-
justify-content: center;
|
|
197
|
-
min-width: 20px;
|
|
198
|
-
height: 20px;
|
|
199
|
-
padding: 0 6px;
|
|
200
|
-
flex-shrink: 0;
|
|
201
|
-
border-radius: 999px;
|
|
202
|
-
background: color-mix(in oklch, var(--warn), var(--panel) 72%);
|
|
203
|
-
color: var(--text);
|
|
204
|
-
font-size: 11px;
|
|
205
|
-
font-weight: 640;
|
|
206
|
-
font-variant-numeric: tabular-nums;
|
|
207
|
-
}
|
|
208
|
-
.ib-group-desc { color: var(--soft); }
|
|
209
|
-
.ib-group ul {
|
|
210
|
-
margin: 5px 0 0;
|
|
211
|
-
padding: 0;
|
|
212
|
-
list-style: none;
|
|
213
|
-
display: grid;
|
|
214
|
-
gap: 3px;
|
|
215
|
-
}
|
|
216
|
-
.ib-group li {
|
|
217
|
-
display: flex;
|
|
218
|
-
align-items: center;
|
|
219
|
-
gap: 8px;
|
|
220
|
-
min-height: 22px;
|
|
221
|
-
}
|
|
222
|
-
.ib-dir {
|
|
223
|
-
font-family: var(--mono, ui-monospace, monospace);
|
|
224
|
-
font-weight: 560;
|
|
225
|
-
}
|
|
226
|
-
.ib-more {
|
|
227
|
-
color: var(--soft);
|
|
228
|
-
font-size: 12px;
|
|
229
|
-
}
|
|
230
|
-
.ib-copy {
|
|
231
|
-
display: inline-flex;
|
|
232
|
-
align-items: center;
|
|
233
|
-
justify-content: center;
|
|
234
|
-
width: 22px;
|
|
235
|
-
height: 22px;
|
|
236
|
-
flex-shrink: 0;
|
|
237
|
-
border: 1px solid var(--line);
|
|
238
|
-
border-radius: 5px;
|
|
239
|
-
background: var(--panel);
|
|
240
|
-
color: var(--muted);
|
|
241
|
-
cursor: pointer;
|
|
242
|
-
transition: color 0.12s, border-color 0.12s;
|
|
243
|
-
}
|
|
244
|
-
.ib-copy svg { width: 12px; height: 12px; stroke: currentColor; fill: none; stroke-width: 2; }
|
|
245
|
-
.ib-copy:hover { color: var(--text); border-color: var(--line-strong); }
|
|
246
|
-
.ib-copy.is-copied { color: var(--good); border-color: color-mix(in oklch, var(--good), var(--line) 40%); }
|
|
247
|
-
.ib-toggle {
|
|
248
|
-
display: inline-flex;
|
|
249
|
-
align-items: center;
|
|
250
|
-
gap: 4px;
|
|
251
|
-
margin-top: 10px;
|
|
252
|
-
border: 0;
|
|
253
|
-
background: none;
|
|
254
|
-
color: var(--muted);
|
|
255
|
-
font: inherit;
|
|
256
|
-
font-size: 12px;
|
|
257
|
-
cursor: pointer;
|
|
258
|
-
padding: 0;
|
|
259
|
-
}
|
|
260
|
-
.ib-toggle svg { width: 12px; height: 12px; transition: transform 0.12s; }
|
|
261
|
-
.ib-toggle svg.is-open { transform: rotate(90deg); }
|
|
262
|
-
.ib-toggle:hover { color: var(--text); }
|
|
263
|
-
|
|
264
|
-
.section-head {
|
|
265
|
-
display: flex;
|
|
266
|
-
align-items: end;
|
|
267
|
-
justify-content: space-between;
|
|
268
|
-
gap: 18px;
|
|
269
|
-
margin-bottom: 16px;
|
|
270
|
-
}
|
|
271
|
-
h2 {
|
|
272
|
-
margin: 0;
|
|
273
|
-
font-size: clamp(24px, 2.6vw, 31px);
|
|
274
|
-
line-height: 1.1;
|
|
275
|
-
font-weight: 720;
|
|
276
|
-
}
|
|
277
|
-
.controls {
|
|
278
|
-
display: flex;
|
|
279
|
-
gap: 8px;
|
|
280
|
-
align-items: center;
|
|
281
|
-
flex-wrap: wrap;
|
|
282
|
-
}
|
|
283
|
-
.search {
|
|
284
|
-
height: 36px;
|
|
285
|
-
min-width: 320px;
|
|
286
|
-
border: 1px solid var(--line);
|
|
287
|
-
border-radius: 7px;
|
|
288
|
-
padding: 0 12px;
|
|
289
|
-
background: var(--panel);
|
|
290
|
-
color: var(--text);
|
|
291
|
-
outline: none;
|
|
292
|
-
}
|
|
293
|
-
.search:focus { border-color: var(--focus); }
|
|
294
|
-
.table-wrap {
|
|
295
|
-
border: 1px solid var(--line);
|
|
296
|
-
border-radius: 8px;
|
|
297
|
-
overflow: auto;
|
|
298
|
-
background: var(--panel);
|
|
299
|
-
}
|
|
300
|
-
table {
|
|
301
|
-
width: 100%;
|
|
302
|
-
border-collapse: collapse;
|
|
303
|
-
min-width: 1040px;
|
|
304
|
-
}
|
|
305
|
-
th, td {
|
|
306
|
-
border-bottom: 1px solid var(--line);
|
|
307
|
-
padding: 0 16px;
|
|
308
|
-
height: 52px;
|
|
309
|
-
text-align: left;
|
|
310
|
-
vertical-align: middle;
|
|
311
|
-
font-size: 14px;
|
|
312
|
-
}
|
|
313
|
-
th {
|
|
314
|
-
color: var(--muted);
|
|
315
|
-
font-weight: 500;
|
|
316
|
-
background: var(--panel);
|
|
317
|
-
position: sticky;
|
|
318
|
-
top: 0;
|
|
319
|
-
z-index: 2;
|
|
320
|
-
}
|
|
321
|
-
tbody tr:last-child td { border-bottom: 0; }
|
|
322
|
-
.section-sub-head {
|
|
323
|
-
display: flex;
|
|
324
|
-
align-items: center;
|
|
325
|
-
justify-content: space-between;
|
|
326
|
-
gap: 18px;
|
|
327
|
-
margin-bottom: 16px;
|
|
328
|
-
min-height: 36px;
|
|
329
|
-
}
|
|
330
|
-
.group-detail-label {
|
|
331
|
-
font-size: 15px;
|
|
332
|
-
font-weight: 680;
|
|
333
|
-
color: var(--text);
|
|
334
|
-
}
|
|
335
117
|
.name { font-weight: 610; }
|
|
336
|
-
.sub { color: var(--muted); font-size: 12px; margin-top: 3px; }
|
|
337
118
|
.num {
|
|
338
119
|
font-variant-numeric: tabular-nums;
|
|
339
120
|
white-space: nowrap;
|
|
@@ -343,12 +124,6 @@ tbody tr:last-child td { border-bottom: 0; }
|
|
|
343
124
|
.bad { color: var(--bad); }
|
|
344
125
|
.warn { color: var(--warn); }
|
|
345
126
|
.infra-err { color: var(--warn); }
|
|
346
|
-
.eval-id {
|
|
347
|
-
display: flex;
|
|
348
|
-
align-items: center;
|
|
349
|
-
overflow-wrap: anywhere;
|
|
350
|
-
font-weight: 560;
|
|
351
|
-
}
|
|
352
127
|
.copy-all-errors {
|
|
353
128
|
height: 36px;
|
|
354
129
|
display: inline-flex;
|
|
@@ -702,41 +477,10 @@ pre {
|
|
|
702
477
|
font-size: 12px;
|
|
703
478
|
line-height: 1.5;
|
|
704
479
|
}
|
|
705
|
-
.empty {
|
|
706
|
-
border: 1px dashed var(--line-strong);
|
|
707
|
-
border-radius: 8px;
|
|
708
|
-
padding: 40px 24px;
|
|
709
|
-
color: var(--muted);
|
|
710
|
-
text-align: center;
|
|
711
|
-
background: var(--panel);
|
|
712
|
-
}
|
|
713
|
-
a.brand { text-decoration: none; color: inherit; }
|
|
714
|
-
a.brand:hover { opacity: 0.75; }
|
|
715
|
-
.traces-entry {
|
|
716
|
-
border: 1px solid var(--line);
|
|
717
|
-
border-radius: 8px;
|
|
718
|
-
background: var(--panel);
|
|
719
|
-
margin-bottom: 12px;
|
|
720
|
-
padding: 14px 18px;
|
|
721
|
-
}
|
|
722
|
-
.traces-entry-head {
|
|
723
|
-
display: flex;
|
|
724
|
-
align-items: baseline;
|
|
725
|
-
gap: 12px;
|
|
726
|
-
margin-bottom: 8px;
|
|
727
|
-
font-size: 13px;
|
|
728
|
-
}
|
|
729
|
-
.traces-verdict { font-weight: 600; min-width: 52px; }
|
|
730
|
-
.traces-exp { color: var(--muted); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
731
|
-
.traces-dur { color: var(--soft); }
|
|
732
480
|
@media (max-width: 760px) {
|
|
733
481
|
.topbar { padding: 0 20px; }
|
|
734
482
|
.lang-switch button { min-width: 40px; }
|
|
735
483
|
main { width: min(100vw - 28px, 1120px); padding-top: 42px; }
|
|
736
|
-
.hero { text-align: left; margin-left: 0; }
|
|
737
|
-
.meta { justify-content: flex-start; }
|
|
738
|
-
.section-head { align-items: stretch; flex-direction: column; }
|
|
739
|
-
.search { width: 100%; min-width: 0; }
|
|
740
484
|
.detail { padding: 18px 16px 20px; }
|
|
741
485
|
.config-strip { gap: 6px; }
|
|
742
486
|
.detail-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 10px; }
|
|
@@ -1194,8 +938,4 @@ li.tnode-row::before { content: ""; width: 4px; flex-shrink: 0; }
|
|
|
1194
938
|
color: var(--soft);
|
|
1195
939
|
font-size: 12px;
|
|
1196
940
|
}
|
|
1197
|
-
/* 品牌行:hero 之下、品牌蓝,恒带官网链接(shell.md「行为约束」)。 */
|
|
1198
|
-
.hero .powered-by { display: block; margin-top: 16px; font-size: 12px; }
|
|
1199
|
-
.hero .powered-by a { color: var(--blue); text-decoration: none; }
|
|
1200
|
-
.hero .powered-by a:hover { text-decoration: underline; }
|
|
1201
941
|
.nre-page-error pre { white-space: pre-wrap; }
|
|
@@ -139,33 +139,55 @@ describe("resolveViewInput · 输入语义", () => {
|
|
|
139
139
|
});
|
|
140
140
|
|
|
141
141
|
|
|
142
|
-
/** 单页报告(
|
|
142
|
+
/** 单页报告(树形态 --report)的报告槽 HTML:规范化后唯一页 id 恒为 `report`。 */
|
|
143
143
|
function slotHtml(scan: ViewScan): { en: string; "zh-CN": string } {
|
|
144
144
|
expect(scan.reportPages).toHaveLength(1);
|
|
145
145
|
expect(scan.reportPages[0]!.id).toBe("report");
|
|
146
146
|
return scan.reportPages[0]!.html as { en: string; "zh-CN": string };
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
/** 多页定义(裸跑装载的内建报告是三页)按页 id 取报告槽 HTML。 */
|
|
150
|
+
function pageHtml(scan: ViewScan, id: string): { en: string; "zh-CN": string } {
|
|
151
|
+
const page = scan.reportPages.find((p) => p.id === id);
|
|
152
|
+
expect(page, `page ${id}`).toBeTruthy();
|
|
153
|
+
return page!.html as { en: string; "zh-CN": string };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** 裸跑的首页(内建报告的 report 页):先断言内建三页齐全,再取首页 HTML。 */
|
|
157
|
+
function bareReportHtml(scan: ViewScan): { en: string; "zh-CN": string } {
|
|
158
|
+
expect(scan.reportPages.map((p) => p.id)).toEqual(["report", "attempts", "traces"]);
|
|
159
|
+
return pageHtml(scan, "report");
|
|
160
|
+
}
|
|
161
|
+
|
|
149
162
|
// ───────────────────────── 组合语义(与 show 对齐) ─────────────────────────
|
|
150
163
|
|
|
151
164
|
describe("loadViewScan · 组合语义", () => {
|
|
152
|
-
it("
|
|
165
|
+
it("位置前缀收窄对全部页生效(含内建 Attempts 页);attempt 详情路由对完整结果根解析,被滤掉的深链仍可达", async () => {
|
|
153
166
|
const root = await seedRoot();
|
|
154
167
|
const scan = await loadViewScan(root, { patterns: ["weather"] });
|
|
155
168
|
const { viewData } = scan;
|
|
156
|
-
const reportHtml =
|
|
157
|
-
//
|
|
169
|
+
const reportHtml = bareReportHtml(scan);
|
|
170
|
+
// 报告页:两实验都只剩 weather/brooklyn 一题,范围外的失败不再出现。
|
|
158
171
|
expect(reportHtml.en).toContain("compare/bub");
|
|
159
172
|
expect(reportHtml.en).toContain("compare/codex");
|
|
160
173
|
expect(reportHtml.en).not.toContain("fixtures/button");
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
expect(
|
|
174
|
+
// Attempts / Traces 是普通页,共享同一收窄后 Scope:行集同样缩小。
|
|
175
|
+
expect(pageHtml(scan, "attempts").en).not.toContain("fixtures/button");
|
|
176
|
+
expect(pageHtml(scan, "traces").en).not.toContain("fixtures/button");
|
|
177
|
+
const bare = await loadViewScan(root);
|
|
178
|
+
expect(pageHtml(bare, "attempts").en).toContain("fixtures/button"); // 不收窄时在场,对照
|
|
179
|
+
// attempt 详情路由不是页:viewData.snapshots 全量通道保留,被滤掉的 attempt 深链仍解析成功。
|
|
180
|
+
const filteredOut = viewData.snapshots
|
|
181
|
+
.flatMap((s) => s.results)
|
|
182
|
+
.find((r) => r.id === "fixtures/button");
|
|
183
|
+
expect(filteredOut?.locator).toBeTruthy();
|
|
184
|
+
const { resolveAttemptLocator } = await import("./app/lib/attempt-route.ts");
|
|
185
|
+
expect(resolveAttemptLocator(viewData.snapshots, filteredOut!.locator!)).toBe(filteredOut);
|
|
164
186
|
});
|
|
165
187
|
|
|
166
188
|
it("--experiment 过滤:报告槽 Selection 只留该实验", async () => {
|
|
167
189
|
const root = await seedRoot();
|
|
168
|
-
const reportHtml =
|
|
190
|
+
const reportHtml = bareReportHtml(await loadViewScan(root, { experiment: "compare/codex" }));
|
|
169
191
|
expect(reportHtml.en).toContain("compare/codex");
|
|
170
192
|
expect(reportHtml.en).not.toContain("compare/bub");
|
|
171
193
|
});
|
|
@@ -177,34 +199,43 @@ describe("loadViewScan · 组合语义", () => {
|
|
|
177
199
|
await expect(loadViewScan(root, { experiment: "nosuch" })).rejects.toBeInstanceOf(ViewInputError);
|
|
178
200
|
});
|
|
179
201
|
|
|
180
|
-
it("全部缺省:报告槽使用现刻水位口径(与 show 相同的合成规则)", async () => {
|
|
202
|
+
it("全部缺省:报告槽使用现刻水位口径(与 show 相同的合成规则);导航页即内建三页", async () => {
|
|
181
203
|
const root = await seedRoot();
|
|
182
|
-
const
|
|
204
|
+
const scan = await loadViewScan(root);
|
|
205
|
+
const reportHtml = bareReportHtml(scan);
|
|
183
206
|
// 默认报告的榜单含两个实验;官方组件的稳定类名在场。
|
|
184
207
|
expect(reportHtml.en).toContain("compare/bub");
|
|
185
208
|
expect(reportHtml.en).toContain("compare/codex");
|
|
186
209
|
expect(reportHtml.en).toContain("nre-");
|
|
210
|
+
// 导航 = 报告定义声明的页(声明序),宿主不追加:裸 view 的三个 tab 就是内建三页。
|
|
211
|
+
expect(scan.viewData.report?.pages.map((p) => p.id)).toEqual(["report", "attempts", "traces"]);
|
|
212
|
+
expect(scan.viewData.report?.pages.map((p) => p.title)).toEqual([
|
|
213
|
+
{ en: "Report", "zh-CN": "报告" },
|
|
214
|
+
"Attempts",
|
|
215
|
+
{ en: "Traces", "zh-CN": "追踪" },
|
|
216
|
+
]);
|
|
187
217
|
});
|
|
188
218
|
});
|
|
189
219
|
|
|
190
220
|
// ─────────────────── 报告槽恒在:裸跑 ≡ --report <ExperimentComparison> ───────────────────
|
|
191
221
|
|
|
192
222
|
describe("loadViewScan · 默认报告槽(裸跑)", () => {
|
|
193
|
-
it("裸跑产出的报告槽 HTML 与 --report <re-export
|
|
223
|
+
it("裸跑产出的报告槽 HTML 与 --report <re-export 内建报告的文件> 逐页完全一致(双语)", async () => {
|
|
194
224
|
const root = await seedRoot();
|
|
195
|
-
const bare =
|
|
196
|
-
const viaReport =
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
|
|
225
|
+
const bare = await loadViewScan(root);
|
|
226
|
+
const viaReport = await loadViewScan(root, {
|
|
227
|
+
report: { path: DEFAULT_REPORT_REEXPORT, cwd: root },
|
|
228
|
+
});
|
|
229
|
+
expect(viaReport.reportPages.map((p) => p.id)).toEqual(bare.reportPages.map((p) => p.id));
|
|
230
|
+
for (const pageId of ["report", "attempts", "traces"]) {
|
|
231
|
+
expect(pageHtml(viaReport, pageId).en).toBe(pageHtml(bare, pageId).en);
|
|
232
|
+
expect(pageHtml(viaReport, pageId)["zh-CN"]).toBe(pageHtml(bare, pageId)["zh-CN"]);
|
|
233
|
+
}
|
|
203
234
|
});
|
|
204
235
|
|
|
205
236
|
it("报告槽双语渲染:同一棵树按 locale 渲染两遍,chrome 文案分语言、数据不分语言", async () => {
|
|
206
237
|
const root = await seedRoot();
|
|
207
|
-
const reportHtml =
|
|
238
|
+
const reportHtml = bareReportHtml(await loadViewScan(root));
|
|
208
239
|
expect(reportHtml.en).toContain("End-to-end pass rate"); // ExperimentList 主行(en)
|
|
209
240
|
expect(reportHtml["zh-CN"]).toContain("端到端成功率"); // ExperimentList 主行(zh-CN)
|
|
210
241
|
for (const html of [reportHtml.en, reportHtml["zh-CN"]]) {
|
|
@@ -216,11 +247,17 @@ describe("loadViewScan · 默认报告槽(裸跑)", () => {
|
|
|
216
247
|
}
|
|
217
248
|
});
|
|
218
249
|
|
|
219
|
-
it("
|
|
250
|
+
it("失败清单与警告住在报告页里:ExperimentList 列出失败,壳的 viewData 不携带统计产物", async () => {
|
|
220
251
|
const root = await seedRoot();
|
|
221
252
|
const scan = await loadViewScan(root);
|
|
222
253
|
const { viewData } = scan;
|
|
223
|
-
expect(
|
|
254
|
+
expect(bareReportHtml(scan).en).toContain("fixtures/button");
|
|
255
|
+
// 品牌行是页内组件(Hero 恒含),不是宿主 chrome:每页 web 面都带官网品牌行。
|
|
256
|
+
for (const pageId of ["report", "attempts", "traces"]) {
|
|
257
|
+
const html = pageHtml(scan, pageId).en;
|
|
258
|
+
expect(html).toContain("nre-powered-by");
|
|
259
|
+
expect(html).toContain("https://niceeval.com/?utm_source=report&utm_medium=powered-by");
|
|
260
|
+
}
|
|
224
261
|
// viewData 只有证据室数据:快照 + skipped + 壳元信息。
|
|
225
262
|
expect(viewData).not.toHaveProperty("overview");
|
|
226
263
|
expect(viewData).not.toHaveProperty("table");
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import type { LazyArtifactType, T } from "../shared.ts";
|
|
3
|
-
import { artifactUrl } from "../lib/artifact-url.ts";
|
|
4
|
-
import { asEvents, asSpans } from "../lib/guards.ts";
|
|
5
|
-
import { Trace } from "./Trace.tsx";
|
|
6
|
-
import { Transcript } from "./Transcript.tsx";
|
|
7
|
-
|
|
8
|
-
export function LazyArtifact({ type, src, autoLoad = false, t }: { type: LazyArtifactType; src: string; autoLoad?: boolean; t: T }) {
|
|
9
|
-
const [open, setOpen] = useState(autoLoad);
|
|
10
|
-
const [loaded, setLoaded] = useState(false);
|
|
11
|
-
const [content, setContent] = useState<unknown>(null);
|
|
12
|
-
const [error, setError] = useState("");
|
|
13
|
-
|
|
14
|
-
const load = async () => {
|
|
15
|
-
if (loaded) return;
|
|
16
|
-
setLoaded(true);
|
|
17
|
-
try {
|
|
18
|
-
const resp = await fetch(artifactUrl(src));
|
|
19
|
-
if (!resp.ok) throw new Error("HTTP " + resp.status);
|
|
20
|
-
const body = await resp.json();
|
|
21
|
-
setContent(body);
|
|
22
|
-
setError("");
|
|
23
|
-
} catch (e) {
|
|
24
|
-
setLoaded(false);
|
|
25
|
-
setError(`${t("trace.loadFailed")} ${String(e)}`);
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
if (autoLoad) void load();
|
|
31
|
-
}, []);
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<details
|
|
35
|
-
className="trace-details"
|
|
36
|
-
open={open}
|
|
37
|
-
onToggle={(e) => {
|
|
38
|
-
const isOpen = e.currentTarget.open;
|
|
39
|
-
setOpen(isOpen);
|
|
40
|
-
if (isOpen) void load();
|
|
41
|
-
}}
|
|
42
|
-
>
|
|
43
|
-
<summary>{type === "transcript" ? t("trace.transcript") : t("trace.timing")}</summary>
|
|
44
|
-
<div className="trace-slot">
|
|
45
|
-
{error ? <div className="trace-span-meta">{error}</div> : !content ? <div className="trace-span-meta">{t("trace.loading")}</div> : null}
|
|
46
|
-
{content && type === "transcript" ? <Transcript events={asEvents(content) ?? []} t={t} /> : null}
|
|
47
|
-
{content && type === "trace" ? <Trace spans={asSpans(content) ?? []} t={t} /> : null}
|
|
48
|
-
</div>
|
|
49
|
-
</details>
|
|
50
|
-
);
|
|
51
|
-
}
|