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/dist/report/report.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// defineReport:唯一可被宿主装载的产物 —— 一层外壳(标题、外链、页脚、head 标签、脚本、样式)加
|
|
2
2
|
// 非空页列表;单页与多页不是两种机制,页数只是列表长度(docs/feature/reports/library/shell.md)。
|
|
3
3
|
// 入参有两级缩写,各有精确展开:树入参 ≡ { content: 树 } ≡ pages: [{ id: "report",
|
|
4
|
-
// title: 内置页名, content: 树 }]。`content`
|
|
4
|
+
// title: 内置页名, content: 树 }]。`content` / `pages` / `extends` 恰好声明一个,没有隐式默认;
|
|
5
|
+
// `extends` 在另一份报告上叠外壳——页归 base、外壳逐字段覆盖,合并在调用时折叠完成,
|
|
6
|
+
// 宿主装载看到的永远是已折叠的普通产物。
|
|
5
7
|
//
|
|
6
8
|
// renderReportToText 是 text 宿主(show)的装载入口;web 宿主(view)的
|
|
7
9
|
// renderReportToStaticHtml 在 ./web.ts(那一侧才 import react-dom)。管线以页为单位执行:
|
|
@@ -13,7 +15,7 @@ const REPORT_DEFINITION = Symbol.for("niceeval.report.definition");
|
|
|
13
15
|
export const DEFAULT_PAGE_ID = "report";
|
|
14
16
|
const DEFAULT_PAGE_TITLE = { en: "Report", "zh-CN": "报告" };
|
|
15
17
|
// ───────────────────────── 装载规范化与静态校验 ─────────────────────────
|
|
16
|
-
const
|
|
18
|
+
const EXTENDS_NEXT_STEP = 'To render the built-in report, write extends: standard (import { standard } from "niceeval/report/built-in").';
|
|
17
19
|
function isReportNodeInput(value) {
|
|
18
20
|
if (value === null || value === undefined || typeof value === "boolean")
|
|
19
21
|
return true;
|
|
@@ -31,7 +33,8 @@ function assertNotDefinition(value, where) {
|
|
|
31
33
|
value.kind === "report" &&
|
|
32
34
|
value[REPORT_DEFINITION] === true) {
|
|
33
35
|
throw new Error(`${where} received a defineReport(...) product, but a report definition is not a report node — the shell cannot nest. ` +
|
|
34
|
-
"Pass the page's tree or component here
|
|
36
|
+
"Pass the page's tree or component here. To layer a shell over another report, write defineReport({ extends: base, … }); " +
|
|
37
|
+
"otherwise export the defineReport product as the file's default export.");
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
function assertLocalizedText(value, where) {
|
|
@@ -155,26 +158,40 @@ export function defineReport(input) {
|
|
|
155
158
|
? { content: input }
|
|
156
159
|
: input;
|
|
157
160
|
if (typeof def !== "object" || def === null) {
|
|
158
|
-
throw new Error("defineReport expects a report tree or a config object ({ title?, links?, footer?, head?, scripts?, styles?, content | pages }). " +
|
|
159
|
-
|
|
161
|
+
throw new Error("defineReport expects a report tree or a config object ({ title?, links?, footer?, head?, scripts?, styles?, content | pages | extends }). " +
|
|
162
|
+
EXTENDS_NEXT_STEP);
|
|
160
163
|
}
|
|
161
164
|
const hasContent = "content" in def && def.content !== undefined;
|
|
162
165
|
const hasPages = "pages" in def && def.pages !== undefined;
|
|
163
|
-
|
|
164
|
-
|
|
166
|
+
const hasExtends = "extends" in def && def.extends !== undefined;
|
|
167
|
+
const declared = [hasContent && '"content"', hasPages && '"pages"', hasExtends && '"extends"'].filter((name) => typeof name === "string");
|
|
168
|
+
if (declared.length > 1) {
|
|
169
|
+
throw new Error(`defineReport got ${declared.join(" and ")} — declare exactly one of "content" (a single tree), "pages" (a multi-page report), or "extends" (another report plus this shell). ${EXTENDS_NEXT_STEP}`);
|
|
165
170
|
}
|
|
166
|
-
if (
|
|
167
|
-
throw new Error(`defineReport got
|
|
171
|
+
if (declared.length === 0) {
|
|
172
|
+
throw new Error(`defineReport got none of "content", "pages" or "extends" — declare exactly one; omission is not a meaningful value, the file must show what renders. ${EXTENDS_NEXT_STEP}`);
|
|
168
173
|
}
|
|
174
|
+
// extends:报告级复用的唯一位置。页归 base,本对象只贡献外壳;base 已经过 defineReport
|
|
175
|
+
// 校验,页不重验。
|
|
176
|
+
let base;
|
|
169
177
|
let pages;
|
|
170
|
-
if (
|
|
178
|
+
if (hasExtends) {
|
|
179
|
+
const candidate = def.extends;
|
|
180
|
+
if (!isReportDefinition(candidate)) {
|
|
181
|
+
throw new Error('defineReport "extends" must be a defineReport(...) product — the base report whose pages this report inherits. ' +
|
|
182
|
+
EXTENDS_NEXT_STEP);
|
|
183
|
+
}
|
|
184
|
+
base = candidate;
|
|
185
|
+
pages = base.pages;
|
|
186
|
+
}
|
|
187
|
+
else if (hasContent) {
|
|
171
188
|
assertNotDefinition(def.content, 'defineReport "content"');
|
|
172
189
|
pages = [{ id: DEFAULT_PAGE_ID, title: DEFAULT_PAGE_TITLE, content: def.content }];
|
|
173
190
|
}
|
|
174
191
|
else {
|
|
175
192
|
const raw = def.pages;
|
|
176
193
|
if (!Array.isArray(raw) || raw.length === 0) {
|
|
177
|
-
throw new Error(`defineReport "pages" must be a non-empty array of { id, title, content }. ${
|
|
194
|
+
throw new Error(`defineReport "pages" must be a non-empty array of { id, title, content }. ${EXTENDS_NEXT_STEP}`);
|
|
178
195
|
}
|
|
179
196
|
const seen = new Set();
|
|
180
197
|
for (const page of raw) {
|
|
@@ -194,34 +211,43 @@ export function defineReport(input) {
|
|
|
194
211
|
assertLocalizedText(def.title, "defineReport title");
|
|
195
212
|
if (def.footer !== undefined)
|
|
196
213
|
assertLocalizedText(def.footer, "defineReport footer");
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
'
|
|
214
|
+
// 外壳合并:声明即整字段覆盖,未声明沿用 base(base 的字段已规范化,不重验)。
|
|
215
|
+
let links;
|
|
216
|
+
if (def.links !== undefined) {
|
|
217
|
+
if (!Array.isArray(def.links))
|
|
218
|
+
throw new Error("defineReport links must be an array of { label, href }.");
|
|
219
|
+
for (const link of def.links) {
|
|
220
|
+
assertLocalizedText(link?.label, "defineReport link label");
|
|
221
|
+
if (typeof link?.href !== "string" || link.href.length === 0) {
|
|
222
|
+
throw new Error("defineReport link href must be a non-empty string URL.");
|
|
223
|
+
}
|
|
224
|
+
// icon 唯一合法形状是 { svg: string }(无类型 JS 传组件 / ReactNode / 裸字符串都在装载期拒绝):
|
|
225
|
+
// 外壳声明经序列化边界进前端,ReactNode 过不去,可序列化是外壳契约的一部分。
|
|
226
|
+
const icon = link.icon;
|
|
227
|
+
if (icon !== undefined) {
|
|
228
|
+
const svg = icon?.svg;
|
|
229
|
+
if (typeof icon !== "object" || icon === null || typeof svg !== "string" || svg.length === 0) {
|
|
230
|
+
throw new Error('defineReport link "icon" must be { svg: string } — an inline SVG string rendered before the label. ' +
|
|
231
|
+
"Components and React nodes are not accepted: the shell declaration crosses a serialization boundary. " +
|
|
232
|
+
'Write e.g. icon: { svg: "<svg …>…</svg>" }.');
|
|
233
|
+
}
|
|
214
234
|
}
|
|
215
235
|
}
|
|
236
|
+
links = def.links;
|
|
216
237
|
}
|
|
238
|
+
else {
|
|
239
|
+
links = base?.links ?? [];
|
|
240
|
+
}
|
|
241
|
+
const title = def.title !== undefined ? def.title : base?.title;
|
|
242
|
+
const footer = def.footer !== undefined ? def.footer : base?.footer;
|
|
217
243
|
const definition = {
|
|
218
244
|
kind: "report",
|
|
219
|
-
...(
|
|
245
|
+
...(title !== undefined ? { title } : {}),
|
|
220
246
|
links: [...links],
|
|
221
|
-
...(
|
|
222
|
-
head: assertHeadTags(def.head),
|
|
223
|
-
scripts: assertAssets(def.scripts, "scripts"),
|
|
224
|
-
styles: assertAssets(def.styles, "styles"),
|
|
247
|
+
...(footer !== undefined ? { footer } : {}),
|
|
248
|
+
head: def.head !== undefined ? assertHeadTags(def.head) : [...(base?.head ?? [])],
|
|
249
|
+
scripts: def.scripts !== undefined ? assertAssets(def.scripts, "scripts") : [...(base?.scripts ?? [])],
|
|
250
|
+
styles: def.styles !== undefined ? assertAssets(def.styles, "styles") : [...(base?.styles ?? [])],
|
|
225
251
|
pages: pages,
|
|
226
252
|
};
|
|
227
253
|
Object.defineProperty(definition, REPORT_DEFINITION, { value: true });
|
|
@@ -283,17 +309,11 @@ export function pickReportPage(definition, pageId) {
|
|
|
283
309
|
}
|
|
284
310
|
return page;
|
|
285
311
|
}
|
|
286
|
-
/**
|
|
287
|
-
* 挑选警告的 text 形态:每条渲染好的 message 前缀 "! ",一行一条。宿主级前置块——
|
|
288
|
-
* 宿主是 warning 的唯一呈现者,组件数据不复制 warning;裸跑 / --report 都在报告顶上
|
|
289
|
-
* 如实报残缺,不静默(docs/feature/reports/architecture.md「Scope 是计算入口」)。
|
|
290
|
-
*/
|
|
291
|
-
function renderScopeWarningsText(scope, _locale) {
|
|
292
|
-
return scope.warnings.map((w) => `! ${w.message}`).join("\n");
|
|
293
|
-
}
|
|
294
312
|
/**
|
|
295
313
|
* text 宿主的装载语义:选页 → resolve(组合展开 + spec 取数,唯一的 await 边界)→ 树校验 →
|
|
296
|
-
* 遍历渲染 text
|
|
314
|
+
* 遍历渲染 text 面。不需要 react-dom。宿主不在报告树外另设警告通道——挑选警告的呈现件是
|
|
315
|
+
* `ScopeWarnings` 组件,内建报告每页都放它,自定义报告放不放是作者义务
|
|
316
|
+
* (docs/feature/reports/architecture.md「Scope 是计算入口」)。
|
|
297
317
|
*/
|
|
298
318
|
export async function renderReportToText(definition, ctx, options) {
|
|
299
319
|
const page = pickReportPage(definition, options?.pageId);
|
|
@@ -305,11 +325,7 @@ export async function renderReportToText(definition, ctx, options) {
|
|
|
305
325
|
memo: new ResolveMemo(),
|
|
306
326
|
});
|
|
307
327
|
validateReportTree(resolved);
|
|
308
|
-
|
|
309
|
-
const body = renderNodeToText(resolved, textCtx);
|
|
310
|
-
return ctx.scope.warnings.length > 0
|
|
311
|
-
? [renderScopeWarningsText(ctx.scope, textCtx.locale), body].join("\n\n")
|
|
312
|
-
: body;
|
|
328
|
+
return renderNodeToText(resolved, createTextContext(options));
|
|
313
329
|
}
|
|
314
330
|
/** 页索引标题行(show 多页索引 / view 导航共用的解析结果):按 locale 解析的标题字符串。 */
|
|
315
331
|
export function reportTitleText(definition, scope, locale) {
|
|
@@ -333,8 +349,8 @@ function experimentCommandFor(ctx) {
|
|
|
333
349
|
}
|
|
334
350
|
/**
|
|
335
351
|
* 渲染一页报告树的 text 面(宿主逐页调用;页选择归宿主):
|
|
336
|
-
* resolve(组合展开 + spec 取数)→ validate → render
|
|
337
|
-
*
|
|
352
|
+
* resolve(组合展开 + spec 取数)→ validate → render。宿主不在报告树外另设警告通道,
|
|
353
|
+
* 挑选警告由页内的 `ScopeWarnings` 组件呈现(内建报告每页都放它)。
|
|
338
354
|
*/
|
|
339
355
|
export async function renderReportTreeToText(tree, ctx, options) {
|
|
340
356
|
const resolved = await resolveReportTree(tree, {
|
|
@@ -350,8 +366,5 @@ export async function renderReportTreeToText(tree, ctx, options) {
|
|
|
350
366
|
? { experimentCommand: experimentCommandFor(options.commandContext) }
|
|
351
367
|
: {}),
|
|
352
368
|
});
|
|
353
|
-
|
|
354
|
-
return ctx.scope.warnings.length > 0
|
|
355
|
-
? [renderScopeWarningsText(ctx.scope, textCtx.locale), body].join("\n\n")
|
|
356
|
-
: body;
|
|
369
|
+
return renderNodeToText(resolved, textCtx);
|
|
357
370
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ScopeWarning } from "../results/types.ts";
|
|
2
|
+
import { type ReportLocale } from "./locale.ts";
|
|
3
|
+
/** kind 表登记的类别:integrity(选中集合的分母可能不对)组排在 freshness(可能过期)之前。 */
|
|
4
|
+
export type WarningCategory = "integrity" | "freshness";
|
|
5
|
+
export interface ScopeWarningGroup {
|
|
6
|
+
category: WarningCategory;
|
|
7
|
+
/** 实验组为 experimentId;kind 组为登记的组头文案(含条数);未登记 kind 用 kind 原文。 */
|
|
8
|
+
title: string;
|
|
9
|
+
/** 每条警告一枚、与 warnings 同序;未登记徽标模板的成员不出徽标。 */
|
|
10
|
+
badges: readonly {
|
|
11
|
+
kind: string;
|
|
12
|
+
text: string;
|
|
13
|
+
}[];
|
|
14
|
+
/** 组内命令去重后恰一条时归组头(复制即推进整组);多条或零条为 null,命令随明细逐条走。 */
|
|
15
|
+
headCommand: string | null;
|
|
16
|
+
/** 原始条目(明细层,message 单源)。 */
|
|
17
|
+
warnings: readonly ScopeWarning[];
|
|
18
|
+
}
|
|
19
|
+
export interface GroupedScopeWarnings {
|
|
20
|
+
/** 分类计数汇总行,任何组数下都产出;web 面用作外层折叠块的 <summary>,text 面只在多组时打印。 */
|
|
21
|
+
summary: string;
|
|
22
|
+
groups: readonly ScopeWarningGroup[];
|
|
23
|
+
/** 警告总条数 ≤ 3 时组级明细默认展开(web 面第二层 <details> 的 open;阈值是行为契约,无开关)。 */
|
|
24
|
+
detailsOpen: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** 明细折叠块的标签(「N 条原始警告」)。 */
|
|
27
|
+
export declare function warningDetailsLabel(locale: ReportLocale, n: number): string;
|
|
28
|
+
export declare function groupScopeWarnings(input: readonly ScopeWarning[], locale: ReportLocale): GroupedScopeWarnings;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// ScopeWarnings 的聚合层:把 Scope 警告按「下一步动作」组织成组,web / text 两面共用
|
|
2
|
+
// (docs/feature/reports/library/site-components.md「聚合轴是动作,不是发生顺序」)。
|
|
3
|
+
// message 是完整叙述的单源,这里只组织、不改写;徽标 / 组头文案按 kind 表登记的模板
|
|
4
|
+
// (docs/feature/results/library.md「警告 kind 全集」)经 locale 词典渲染,未登记的 kind
|
|
5
|
+
// 回退为单独成组、逐条 message 原样。
|
|
6
|
+
import { gapParts } from "../results/select.js";
|
|
7
|
+
import { localeText } from "./locale.js";
|
|
8
|
+
const CATEGORY = {
|
|
9
|
+
"partial-coverage": "integrity",
|
|
10
|
+
"unfinished-snapshot": "integrity",
|
|
11
|
+
"unreadable-snapshot": "integrity",
|
|
12
|
+
"stale-snapshot": "freshness",
|
|
13
|
+
};
|
|
14
|
+
/** 实验作用域且登记了徽标模板的 kind 才进实验组;其余(含未登记 kind)按 kind 聚合。 */
|
|
15
|
+
const EXPERIMENT_KINDS = new Set(["partial-coverage", "stale-snapshot", "unfinished-snapshot"]);
|
|
16
|
+
function pluralText(locale, base, n) {
|
|
17
|
+
return localeText(locale, `${base}.${n === 1 ? "one" : "other"}`, { n });
|
|
18
|
+
}
|
|
19
|
+
/** 明细折叠块的标签(「N 条原始警告」)。 */
|
|
20
|
+
export function warningDetailsLabel(locale, n) {
|
|
21
|
+
return pluralText(locale, "warnings.details", n);
|
|
22
|
+
}
|
|
23
|
+
function gapText(locale, fromIso, toIso) {
|
|
24
|
+
const { n, unit } = gapParts(fromIso, toIso);
|
|
25
|
+
return localeText(locale, `warnings.gap.${unit}.${n === 1 ? "one" : "other"}`, { n });
|
|
26
|
+
}
|
|
27
|
+
function badgeText(w, locale) {
|
|
28
|
+
switch (w.kind) {
|
|
29
|
+
case "partial-coverage":
|
|
30
|
+
return localeText(locale, "warnings.badge.partialCoverage", {
|
|
31
|
+
covered: String(w.covered),
|
|
32
|
+
total: String(w.total),
|
|
33
|
+
});
|
|
34
|
+
case "stale-snapshot":
|
|
35
|
+
return localeText(locale, "warnings.badge.staleSnapshot", {
|
|
36
|
+
gap: gapText(locale, String(w.startedAt), String(w.latestStartedAt)),
|
|
37
|
+
});
|
|
38
|
+
case "unfinished-snapshot":
|
|
39
|
+
return localeText(locale, "warnings.badge.unfinishedSnapshot");
|
|
40
|
+
default:
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** 组内命令去重:恰一条时它就是「复制即推进整组」的组头命令。 */
|
|
45
|
+
function dedupeCommand(members) {
|
|
46
|
+
const commands = new Set(members.map((w) => w.command).filter((c) => typeof c === "string" && c !== ""));
|
|
47
|
+
return commands.size === 1 ? [...commands][0] : null;
|
|
48
|
+
}
|
|
49
|
+
function groupCategory(members) {
|
|
50
|
+
return members.some((w) => (CATEGORY[w.kind] ?? "integrity") === "integrity") ? "integrity" : "freshness";
|
|
51
|
+
}
|
|
52
|
+
export function groupScopeWarnings(input, locale) {
|
|
53
|
+
const warnings = input;
|
|
54
|
+
const byExperiment = new Map();
|
|
55
|
+
const byKind = new Map();
|
|
56
|
+
for (const w of warnings) {
|
|
57
|
+
if (EXPERIMENT_KINDS.has(w.kind) && typeof w.experimentId === "string") {
|
|
58
|
+
const members = byExperiment.get(w.experimentId) ?? [];
|
|
59
|
+
members.push(w);
|
|
60
|
+
byExperiment.set(w.experimentId, members);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
const members = byKind.get(w.kind) ?? [];
|
|
64
|
+
members.push(w);
|
|
65
|
+
byKind.set(w.kind, members);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const groups = [];
|
|
69
|
+
for (const [experimentId, members] of byExperiment) {
|
|
70
|
+
groups.push({
|
|
71
|
+
category: groupCategory(members),
|
|
72
|
+
title: experimentId,
|
|
73
|
+
badges: members
|
|
74
|
+
.map((w) => ({ kind: w.kind, text: badgeText(w, locale) }))
|
|
75
|
+
.filter((b) => b.text !== null),
|
|
76
|
+
headCommand: dedupeCommand(members),
|
|
77
|
+
warnings: members,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
for (const [kind, members] of byKind) {
|
|
81
|
+
groups.push({
|
|
82
|
+
category: CATEGORY[kind] ?? "integrity",
|
|
83
|
+
title: kind === "unreadable-snapshot" ? pluralText(locale, "warnings.group.unreadableSnapshot", members.length) : kind,
|
|
84
|
+
badges: [],
|
|
85
|
+
headCommand: dedupeCommand(members),
|
|
86
|
+
warnings: members,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// 稳定排序:integrity 在前,同类别保持首次出现顺序。
|
|
90
|
+
const rank = (c) => (c === "integrity" ? 0 : 1);
|
|
91
|
+
groups.sort((a, b) => rank(a.category) - rank(b.category));
|
|
92
|
+
const parts = [];
|
|
93
|
+
if (byExperiment.size > 0)
|
|
94
|
+
parts.push(pluralText(locale, "warnings.summary.experiments", byExperiment.size));
|
|
95
|
+
for (const [kind, members] of byKind) {
|
|
96
|
+
parts.push(kind === "unreadable-snapshot"
|
|
97
|
+
? pluralText(locale, "warnings.group.unreadableSnapshot", members.length)
|
|
98
|
+
: `${kind} ×${members.length}`);
|
|
99
|
+
}
|
|
100
|
+
return { summary: parts.join(" · "), groups, detailsOpen: warnings.length <= 3 };
|
|
101
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { AttemptListItem, DeltaData, EvalListItem, ExperimentComparisonData, ExperimentListItem, LineData, MatrixData, MetricCell, ScatterData, ScopeSummaryData, ScoreboardData, TableData, VerdictTally } from "../types.ts";
|
|
1
|
+
import type { AttemptListItem, DeltaData, EvalListItem, ExperimentComparisonData, ExperimentListItem, HeroData, LineData, MatrixData, MetricCell, ScatterData, ScopeSummaryData, ScopeWarning, ScoreboardData, TableData, TraceWaterfallRow, VerdictTally } from "../types.ts";
|
|
2
|
+
import type { LocalizedText } from "../locale.ts";
|
|
2
3
|
import type { TextContext } from "../tree.ts";
|
|
3
4
|
import { type ReportLocale } from "../locale.ts";
|
|
4
5
|
/** 格子的文本形态:缺数据 —,覆盖不全带 samples/total 角标;display 按 locale 解析。 */
|
|
@@ -25,3 +26,20 @@ export declare function deltaText(data: DeltaData, ctx: TextContext): string;
|
|
|
25
26
|
export declare function experimentListText(items: readonly ExperimentListItem[], ctx: TextContext, relativeTo?: string): string;
|
|
26
27
|
export declare function evalListText(items: readonly EvalListItem[], ctx: TextContext): string;
|
|
27
28
|
export declare function attemptListText(items: readonly AttemptListItem[], total: number | undefined, ctx: TextContext): string;
|
|
29
|
+
/**
|
|
30
|
+
* HeroCard 的 text 面:标题行 + meta 行(最后运行时间;空范围为内置「暂无运行」文案;
|
|
31
|
+
* 多快照时标注合成来源),不含品牌行(品牌行是纯 web 件,text 面零输出)。
|
|
32
|
+
*/
|
|
33
|
+
export declare function heroCardText(title: LocalizedText, data: HeroData, ctx: TextContext): string;
|
|
34
|
+
/**
|
|
35
|
+
* ScopeWarnings 的 text 面:按动作聚合(../scope-warnings.ts,与 web 面共用),同构但不折叠——
|
|
36
|
+
* 多组时首行 "! <分类计数汇总>";每组一行组头 "! <标题> — <徽标> → <组头命令>",其下缩进
|
|
37
|
+
* 逐条原样打印 message(已以下一步收尾,不截断掉尾段)。空警告集零输出。
|
|
38
|
+
*/
|
|
39
|
+
export declare function scopeWarningsText(warnings: readonly ScopeWarning[], ctx: TextContext): string;
|
|
40
|
+
/**
|
|
41
|
+
* TraceWaterfall 的 text 面:每 attempt 一行——locator、总耗时(缺 trace 如实显示缺失)、
|
|
42
|
+
* 顶层 span 计数与失败标记,行尾是可复制的 `--timing` 下钻命令(经宿主注入的
|
|
43
|
+
* attemptCommand 通道拼出,携带宿主上下文)。attempt 有选择器,索引终结于可执行命令。
|
|
44
|
+
*/
|
|
45
|
+
export declare function traceWaterfallText(rows: readonly TraceWaterfallRow[], ctx: TextContext): string;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// 零 react、零 IO、纯同步 —— 这是 text 宿主不需要 react-dom 的那一半。
|
|
4
4
|
// chrome 文案(注脚、verdict 词、截断提示)经 ctx.locale 查 locale 字典;
|
|
5
5
|
// 数据 display 是 LocalizedText,按 LocalizedText 回退规则取值。
|
|
6
|
+
import { groupScopeWarnings } from "../scope-warnings.js";
|
|
6
7
|
import { experimentDisplayName, fitFailureSummary, formatDurationMs, formatMetricValue, formatPlainNumber, formatUSD, verdictMark, } from "../format.js";
|
|
7
8
|
import { countText, localeText, resolveLocalizedText, resolveMetricLabel, } from "../locale.js";
|
|
8
9
|
import { indentBlock, padDisplay, stringWidth, textBar, wrapDisplay } from "./layout.js";
|
|
@@ -608,3 +609,63 @@ export function attemptListText(items, total, ctx) {
|
|
|
608
609
|
blocks.push(localeText(locale, "attemptList.truncatedText", { n: remaining }));
|
|
609
610
|
return blocks.join("\n\n");
|
|
610
611
|
}
|
|
612
|
+
// ───────────────────────── 站点组件(HeroCard / ScopeWarnings / TraceWaterfall)─────────────────────────
|
|
613
|
+
/**
|
|
614
|
+
* HeroCard 的 text 面:标题行 + meta 行(最后运行时间;空范围为内置「暂无运行」文案;
|
|
615
|
+
* 多快照时标注合成来源),不含品牌行(品牌行是纯 web 件,text 面零输出)。
|
|
616
|
+
*/
|
|
617
|
+
export function heroCardText(title, data, ctx) {
|
|
618
|
+
const locale = ctx.locale;
|
|
619
|
+
const meta = data.latestStartedAt === null
|
|
620
|
+
? localeText(locale, "hero.noRuns")
|
|
621
|
+
: [
|
|
622
|
+
localeText(locale, "hero.lastRun", { time: formatDateTimeMinute(data.latestStartedAt) }),
|
|
623
|
+
...(data.snapshots > 1 ? [localeText(locale, "hero.composedSnapshots", { n: data.snapshots })] : []),
|
|
624
|
+
].join(" · ");
|
|
625
|
+
return `${resolveLocalizedText(title, locale)}\n${meta}`;
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* ScopeWarnings 的 text 面:按动作聚合(../scope-warnings.ts,与 web 面共用),同构但不折叠——
|
|
629
|
+
* 多组时首行 "! <分类计数汇总>";每组一行组头 "! <标题> — <徽标> → <组头命令>",其下缩进
|
|
630
|
+
* 逐条原样打印 message(已以下一步收尾,不截断掉尾段)。空警告集零输出。
|
|
631
|
+
*/
|
|
632
|
+
export function scopeWarningsText(warnings, ctx) {
|
|
633
|
+
if (warnings.length === 0)
|
|
634
|
+
return "";
|
|
635
|
+
const { summary, groups } = groupScopeWarnings(warnings, ctx.locale);
|
|
636
|
+
const lines = [];
|
|
637
|
+
// 汇总行只在多组时打印;单组时组头即汇总,不另起一行(web 面则恒以汇总行作外层 <summary>)。
|
|
638
|
+
if (groups.length > 1)
|
|
639
|
+
lines.push(`! ${summary}`);
|
|
640
|
+
for (const group of groups) {
|
|
641
|
+
const badges = group.badges.length > 0 ? ` — ${group.badges.map((b) => b.text).join(" · ")}` : "";
|
|
642
|
+
const command = group.headCommand !== null ? ` → ${group.headCommand}` : "";
|
|
643
|
+
lines.push(`! ${group.title}${badges}${command}`);
|
|
644
|
+
for (const w of group.warnings)
|
|
645
|
+
lines.push(`! ${w.message}`);
|
|
646
|
+
}
|
|
647
|
+
return lines.join("\n");
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* TraceWaterfall 的 text 面:每 attempt 一行——locator、总耗时(缺 trace 如实显示缺失)、
|
|
651
|
+
* 顶层 span 计数与失败标记,行尾是可复制的 `--timing` 下钻命令(经宿主注入的
|
|
652
|
+
* attemptCommand 通道拼出,携带宿主上下文)。attempt 有选择器,索引终结于可执行命令。
|
|
653
|
+
*/
|
|
654
|
+
export function traceWaterfallText(rows, ctx) {
|
|
655
|
+
const locale = ctx.locale;
|
|
656
|
+
if (rows.length === 0)
|
|
657
|
+
return localeText(locale, "traceWaterfall.empty");
|
|
658
|
+
return rows
|
|
659
|
+
.map((row) => {
|
|
660
|
+
const failedSpans = row.spans.filter((span) => span.failed).length;
|
|
661
|
+
const parts = [
|
|
662
|
+
row.locator,
|
|
663
|
+
row.evalId,
|
|
664
|
+
row.durationMs === null ? localeText(locale, "traceWaterfall.noTrace") : formatDurationMs(row.durationMs),
|
|
665
|
+
countText(locale, "traceWaterfall.spans", row.spans.length),
|
|
666
|
+
...(failedSpans > 0 ? [`✗ ${countText(locale, "traceWaterfall.failedSpans", failedSpans)}`] : []),
|
|
667
|
+
];
|
|
668
|
+
return `${parts.join(" · ")} ${ctx.attemptCommand(row.locator)} --timing`;
|
|
669
|
+
})
|
|
670
|
+
.join("\n");
|
|
671
|
+
}
|
package/dist/report/tree.js
CHANGED
|
@@ -187,7 +187,12 @@ async function resolveNode(node, state, path) {
|
|
|
187
187
|
return node;
|
|
188
188
|
}
|
|
189
189
|
if (Array.isArray(node)) {
|
|
190
|
-
|
|
190
|
+
const resolved = await Promise.all(node.map((child) => resolveNode(child, state, path)));
|
|
191
|
+
// resolve 重建的 children 数组对 React 是动态列表(JSX 静态 children 的免 key 待遇随重建
|
|
192
|
+
// 丢失);声明序即身份,给缺 key 的元素补声明位 key,免得 web 面渲染刷 key 警告。
|
|
193
|
+
return resolved.map((child, i) => isReportElement(child) && (child.key === undefined || child.key === null)
|
|
194
|
+
? { ...child, key: `.nre-${i}` }
|
|
195
|
+
: child);
|
|
191
196
|
}
|
|
192
197
|
if (!isReportElement(node))
|
|
193
198
|
return node;
|
package/dist/report/types.d.ts
CHANGED
|
@@ -275,6 +275,51 @@ export interface ScopeSummaryData {
|
|
|
275
275
|
/** costUSD 按 attempt 求和;缺失成本不伪造为 0。 */
|
|
276
276
|
totalCostUSD: MetricCell;
|
|
277
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* `HeroCard` 的数据(docs/feature/reports/library/site-components.md):站点标题区的
|
|
280
|
+
* 运行 meta——最后运行时间与快照合成来源。标题不在 data 里,它是站点声明与 Scope 的合成物,
|
|
281
|
+
* 经 `HeroCardProps.title` 传入。
|
|
282
|
+
*/
|
|
283
|
+
export interface HeroData {
|
|
284
|
+
/** Scope 中最新快照的开始时间;空 Scope 为 null,不编造当前时间。 */
|
|
285
|
+
latestStartedAt: string | null;
|
|
286
|
+
/** 贡献当前水位的快照数;大于 1 时 web 面标注「由 N 次运行合成」。 */
|
|
287
|
+
snapshots: number;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* `CopyFixPrompt` 的数据:resolve 期算好的修复 prompt 全文与参与的失败数
|
|
291
|
+
* (docs/feature/reports/library/site-components.md)。
|
|
292
|
+
*/
|
|
293
|
+
export interface CopyFixPromptData {
|
|
294
|
+
/** 修复 prompt 全文;失败逐条含 eval id、主失败摘要与 attempt 下钻命令。 */
|
|
295
|
+
prompt: string;
|
|
296
|
+
/** 参与 prompt 的失败 attempt 数(verdict 为 failed / errored)。 */
|
|
297
|
+
failures: number;
|
|
298
|
+
}
|
|
299
|
+
/** `TraceWaterfall` 一行里的一个顶层 span 摘要(canonical OTel 字段归一后的形态)。 */
|
|
300
|
+
export interface TraceSpanSummary {
|
|
301
|
+
name: string;
|
|
302
|
+
/** 归一后的语义角色;turn 归入 agent,未识别落 other。 */
|
|
303
|
+
kind: "agent" | "model" | "tool" | "other";
|
|
304
|
+
/** 相对该 attempt trace 起点的偏移(毫秒)。 */
|
|
305
|
+
startOffsetMs: number;
|
|
306
|
+
durationMs: number;
|
|
307
|
+
/** span status 为 error 时 true(web 面失败标记的来源)。 */
|
|
308
|
+
failed: boolean;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* `TraceWaterfall` 一行 = 一次 attempt 的执行时间瀑布摘要。只画被测 agent 的原始 span
|
|
312
|
+
* (trace.json);runner 生命周期节点(`result.phases`)不进瀑布,组合视图归 attempt 详情。
|
|
313
|
+
*/
|
|
314
|
+
export interface TraceWaterfallRow {
|
|
315
|
+
experimentId: string;
|
|
316
|
+
evalId: string;
|
|
317
|
+
locator: AttemptLocator;
|
|
318
|
+
/** trace.json 缺失或为空时 null;行照常出现,证据位置如实显示缺失,不猜值。 */
|
|
319
|
+
durationMs: number | null;
|
|
320
|
+
/** 顶层 span 摘要,按 startOffsetMs 升序。 */
|
|
321
|
+
spans: readonly TraceSpanSummary[];
|
|
322
|
+
}
|
|
278
323
|
/** 一个可比组的数据;三个子块都只消费本组快照,不能含其它父目录的引用。 */
|
|
279
324
|
export interface ExperimentComparisonGroupData {
|
|
280
325
|
/** experiment id 的完整父路径;根目录 experiment 使用完整 id。 */
|
package/dist/report/web.d.ts
CHANGED
|
@@ -12,14 +12,15 @@ export interface StaticHtmlOptions {
|
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* web 宿主的装载语义:选页 → resolve(组合展开 + spec 取数,唯一的 await 边界)→
|
|
15
|
-
* 树校验(与 text 宿主同一遍)→ 静态渲染 web
|
|
16
|
-
*
|
|
15
|
+
* 树校验(与 text 宿主同一遍)→ 静态渲染 web 面。宿主不在报告树外另设警告通道——
|
|
16
|
+
* 挑选警告的呈现件是 `ScopeWarnings` 组件,内建报告每页都放它,自定义报告放不放是
|
|
17
|
+
* 作者义务(docs/feature/reports/architecture.md「Scope 是计算入口」)。
|
|
17
18
|
*/
|
|
18
19
|
export declare function renderReportToStaticHtml(definition: ReportDefinition, ctx: ReportHostContext, options?: StaticHtmlOptions): Promise<string>;
|
|
19
20
|
/**
|
|
20
21
|
* 渲染一页报告树的 web 面(宿主逐页调用;页选择归宿主):resolve → validate → 静态渲染。
|
|
21
|
-
*
|
|
22
|
-
*
|
|
22
|
+
* 挑选警告由页内的 `ScopeWarnings` 组件呈现,宿主不前置任何树外块。
|
|
23
|
+
* ctx.report 是宿主规范化后的声明。
|
|
23
24
|
*/
|
|
24
25
|
export declare function renderReportTreeToStaticHtml(tree: import("./tree.ts").ReportNode, ctx: {
|
|
25
26
|
scope: Scope;
|
package/dist/report/web.js
CHANGED
|
@@ -1,26 +1,15 @@
|
|
|
1
1
|
// web 宿主(view --report)的装载入口:同一棵树走 web 面,renderToStaticMarkup 吐静态
|
|
2
2
|
// HTML 烘进查看器的报告槽。只有这一侧真正 import react-dom(import 边界即运行时边界),
|
|
3
3
|
// 所以本文件不从 niceeval/report 的入口 re-export —— 宿主与测试按源路径 import。
|
|
4
|
-
import * as React from "react";
|
|
5
4
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
6
5
|
import { resolveReportTree, runWithWebContext, validateReportTree, ResolveMemo, } from "./tree.js";
|
|
7
6
|
import { DEFAULT_REPORT_LOCALE } from "./locale.js";
|
|
8
7
|
import { buildReportMeta, pickReportPage } from "./report.js";
|
|
9
|
-
/**
|
|
10
|
-
* 挑选警告的 HTML 形态:宿主级前置块(`.nre nre-report-warnings` 外壳内一个
|
|
11
|
-
* `ul.nre-warnings` + `li.nre-warning[data-kind]`,复用 styles.css 已有样式)。
|
|
12
|
-
* 带 `command` 的警告把命令渲染为可复制块(`.nre-warning-command`);无 command 的
|
|
13
|
-
* 只显示 message,不硬造动作。经 renderToStaticMarkup 走 React,文本自动转义。
|
|
14
|
-
*/
|
|
15
|
-
function renderScopeWarningsHtml(scope) {
|
|
16
|
-
return renderToStaticMarkup(React.createElement("div", { className: "nre nre-report-warnings" }, React.createElement("ul", { className: "nre-warnings" }, scope.warnings.map((w, i) => React.createElement("li", { key: i, className: "nre-warning", "data-kind": w.kind }, w.message, "command" in w && w.command
|
|
17
|
-
? React.createElement("code", { className: "nre-warning-command", "data-nre-copy": w.command }, w.command)
|
|
18
|
-
: null)))));
|
|
19
|
-
}
|
|
20
8
|
/**
|
|
21
9
|
* web 宿主的装载语义:选页 → resolve(组合展开 + spec 取数,唯一的 await 边界)→
|
|
22
|
-
* 树校验(与 text 宿主同一遍)→ 静态渲染 web
|
|
23
|
-
*
|
|
10
|
+
* 树校验(与 text 宿主同一遍)→ 静态渲染 web 面。宿主不在报告树外另设警告通道——
|
|
11
|
+
* 挑选警告的呈现件是 `ScopeWarnings` 组件,内建报告每页都放它,自定义报告放不放是
|
|
12
|
+
* 作者义务(docs/feature/reports/architecture.md「Scope 是计算入口」)。
|
|
24
13
|
*/
|
|
25
14
|
export async function renderReportToStaticHtml(definition, ctx, options) {
|
|
26
15
|
const page = pickReportPage(definition, options?.pageId);
|
|
@@ -36,14 +25,12 @@ export async function renderReportToStaticHtml(definition, ctx, options) {
|
|
|
36
25
|
attemptHref: options?.attemptHref ?? ((locator) => `#/attempt/${locator}`),
|
|
37
26
|
locale: options?.locale ?? DEFAULT_REPORT_LOCALE,
|
|
38
27
|
};
|
|
39
|
-
|
|
40
|
-
const warnings = ctx.scope.warnings.length > 0 ? renderScopeWarningsHtml(ctx.scope) : "";
|
|
41
|
-
return warnings + body;
|
|
28
|
+
return runWithWebContext(webCtx, () => renderToStaticMarkup(resolved));
|
|
42
29
|
}
|
|
43
30
|
/**
|
|
44
31
|
* 渲染一页报告树的 web 面(宿主逐页调用;页选择归宿主):resolve → validate → 静态渲染。
|
|
45
|
-
*
|
|
46
|
-
*
|
|
32
|
+
* 挑选警告由页内的 `ScopeWarnings` 组件呈现,宿主不前置任何树外块。
|
|
33
|
+
* ctx.report 是宿主规范化后的声明。
|
|
47
34
|
*/
|
|
48
35
|
export async function renderReportTreeToStaticHtml(tree, ctx, options) {
|
|
49
36
|
const resolved = await resolveReportTree(tree, {
|
|
@@ -57,7 +44,5 @@ export async function renderReportTreeToStaticHtml(tree, ctx, options) {
|
|
|
57
44
|
attemptHref: options?.attemptHref ?? ((locator) => `#/attempt/${locator}`),
|
|
58
45
|
locale: options?.locale ?? DEFAULT_REPORT_LOCALE,
|
|
59
46
|
};
|
|
60
|
-
|
|
61
|
-
const warnings = ctx.scope.warnings.length > 0 ? renderScopeWarningsHtml(ctx.scope) : "";
|
|
62
|
-
return warnings + body;
|
|
47
|
+
return runWithWebContext(webCtx, () => renderToStaticMarkup(resolved));
|
|
63
48
|
}
|
package/dist/results/select.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { AttemptHandle, DedupeWarning, Experiment, Results, Scope, ScopeWarning, Snapshot } from "./types.ts";
|
|
2
2
|
import type { ExperimentRunInfo, JsonValue } from "../types.ts";
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Results.latest() 的实现:每个实验取最新一次快照(= exp.snapshots[0]),生成挑选警告。
|
|
5
|
+
* 收整个 `Results` 而不是裸 `Experiment[]`,是为了同时取 `skipped` / `root` 生成
|
|
6
|
+
* `unreadable-snapshot` 警告(非实验作用域,不受 `opts.experiments` 过滤 —— 那些落盘
|
|
7
|
+
* 本来就没能解析出 experimentId,没有前缀可过滤)。
|
|
8
|
+
*/
|
|
9
|
+
export declare function selectLatest(results: Pick<Results, "experiments" | "skipped" | "root">, opts?: {
|
|
5
10
|
experiments?: string | string[];
|
|
6
11
|
}): Scope;
|
|
7
12
|
/** selectCurrentResults 的范围输入:experiment id 前缀与 eval id 前缀,都可缺省。 */
|
|
@@ -65,3 +70,11 @@ export declare function dedupeAttempts(attempts: AttemptHandle[]): {
|
|
|
65
70
|
export declare function isNewerSnapshot(a: Snapshot, b: Snapshot): boolean;
|
|
66
71
|
/** experiment id 分段前缀过滤(--experiment / latest({ experiments }) 同一语义);包内使用,不进公共 barrel。 */
|
|
67
72
|
export declare function filterExperiments(experiments: Experiment[], filter?: string | string[]): Experiment[];
|
|
73
|
+
/**
|
|
74
|
+
* stale 警告的人话时距:选粒度最大的单位,四舍五入。结构化形态是单源——message 的英文时距
|
|
75
|
+
* 与 ScopeWarnings 徽标的本地化时距都从这里出,阈值不写两份。
|
|
76
|
+
*/
|
|
77
|
+
export declare function gapParts(fromIso: string, toIso: string): {
|
|
78
|
+
n: number;
|
|
79
|
+
unit: "second" | "minute" | "hour" | "day";
|
|
80
|
+
};
|