niceeval 0.11.4-canary.21 → 0.12.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/dist/report/assets/series-encoding.d.ts +36 -0
- package/dist/report/assets/series-encoding.js +104 -0
- package/dist/report/components/entity-lists/compute.js +5 -17
- package/dist/report/components/entity-lists/content.d.ts +2 -0
- package/dist/report/components/entity-lists/content.js +22 -7
- package/dist/report/definition/cell.d.ts +4 -0
- package/dist/report/definition/cell.js +5 -1
- package/dist/report/definition/primitives/chart.js +125 -25
- package/dist/report/definition/primitives.js +3 -2
- package/dist/report/definition/tree.d.ts +5 -2
- package/dist/report/definition/tree.js +1 -0
- package/dist/report/index.d.ts +1 -1
- package/dist/report/model/aggregate.d.ts +10 -1
- package/dist/report/model/aggregate.js +24 -0
- package/dist/report/model/locale.d.ts +0 -1
- package/dist/report/model/locale.js +0 -2
- package/dist/report/model/types.d.ts +8 -0
- package/dist/report/presentation.d.ts +53 -6
- package/dist/report/presentation.js +40 -5
- package/dist/report/react/index.d.ts +1 -1
- package/dist/report/slices/compute.js +52 -4
- package/dist/report/slices/content.d.ts +2 -1
- package/dist/report/slices/content.js +48 -15
- package/dist/report/slices/validate.js +23 -0
- package/package.json +1 -1
- package/src/report/assets/series-encoding.tsx +177 -0
- package/src/report/assets/styles.css +47 -5
- package/src/report/components/compute.test.ts +15 -9
- package/src/report/components/entity-lists/compute.ts +5 -19
- package/src/report/components/entity-lists/content.test.ts +13 -8
- package/src/report/components/entity-lists/content.ts +24 -7
- package/src/report/definition/cell.ts +8 -1
- package/src/report/definition/primitives/chart.tsx +197 -32
- package/src/report/definition/primitives.tsx +8 -3
- package/src/report/definition/tree.ts +6 -2
- package/src/report/index.ts +11 -1
- package/src/report/model/aggregate.ts +26 -0
- package/src/report/model/locale.ts +0 -2
- package/src/report/model/types.ts +8 -0
- package/src/report/presentation.test.tsx +179 -19
- package/src/report/presentation.ts +114 -11
- package/src/report/react/index.tsx +11 -1
- package/src/report/slices/compute.ts +59 -4
- package/src/report/slices/content.ts +55 -16
- package/src/report/slices/delta-table.test.ts +138 -3
- package/src/report/slices/validate.test.ts +29 -0
- package/src/report/slices/validate.ts +16 -0
- package/src/runner/gate-lease.test.ts +38 -14
- package/src/runner/lock.test.ts +40 -16
- package/src/sandbox/compose.ts +3 -1
- package/src/sandbox/dockerfile-build.ts +4 -1
- package/src/sandbox/runtime.ts +4 -1
- package/src/sandbox/vercel.test.ts +12 -3
- package/src/show/index.ts +10 -9
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { SeriesVariant } from "../presentation.ts";
|
|
3
|
+
/** 槽位色板下标 1..6 → CSS 变量,原样交给 fill / stroke / color。 */
|
|
4
|
+
export declare function seriesColorVar(colorIndex: number): string;
|
|
5
|
+
/** SVG pattern id:niceeval-series-pat-v{2|3|4}-c{1..6}。variant 1 是实心,无 pattern。 */
|
|
6
|
+
export declare function seriesPatternId(colorIndex: number, variant: SeriesVariant): string;
|
|
7
|
+
export declare function seriesFill(colorIndex: number, variant: SeriesVariant): string;
|
|
8
|
+
/** 线型变体 → strokeDasharray;variant 1 为空串(实线)。 */
|
|
9
|
+
export declare function seriesStrokeDasharray(variant: SeriesVariant): string;
|
|
10
|
+
export interface SeriesMarkerShape {
|
|
11
|
+
readonly path: string;
|
|
12
|
+
readonly viewBox: string;
|
|
13
|
+
}
|
|
14
|
+
/** 四种 marker 形状(viewBox 0 0 12 12),变体按 docs 槽序表 1–4。 */
|
|
15
|
+
export declare function seriesMarkerShape(variant: SeriesVariant): SeriesMarkerShape;
|
|
16
|
+
export declare function seriesMarker(colorIndex: number, variant: SeriesVariant): {
|
|
17
|
+
readonly path: string;
|
|
18
|
+
readonly viewBox: string;
|
|
19
|
+
readonly fill: string;
|
|
20
|
+
readonly stroke: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* 从 presentation.fill 反推 HTML 柱需要的系列 class。
|
|
24
|
+
* SVG 用 url(#pattern)/var();HTML 柱不能引用 SVG pattern,改挂 series-cN + fill-vN 类,
|
|
25
|
+
* 由 styles.css 用 repeating-linear-gradient 画等效图案,颜色仍走 --series 令牌。
|
|
26
|
+
*/
|
|
27
|
+
export declare function seriesClassesFromFill(fill: string): string;
|
|
28
|
+
/** 从 color 呈现或 series 的 stroke/fill 反推 series-cN 类(图例色点回落)。 */
|
|
29
|
+
export declare function seriesClassFromColorVar(color: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* 页内注入一次的 SVG pattern defs。
|
|
32
|
+
* 18 个 pattern(3 非实心变体 × 6 色);id 与 seriesFill() 产出的 url(#…) 对齐。
|
|
33
|
+
* 子元素直接写 var(--niceeval-color-series-N),不走 currentColor
|
|
34
|
+
* (SVG pattern 内部 currentColor 取自 pattern 自身,引用者传不进来)。
|
|
35
|
+
*/
|
|
36
|
+
export declare function SeriesPatternDefs(): ReactNode;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/** 槽位色板下标 1..6 → CSS 变量,原样交给 fill / stroke / color。 */
|
|
3
|
+
export function seriesColorVar(colorIndex) {
|
|
4
|
+
if (!Number.isInteger(colorIndex) || colorIndex < 1 || colorIndex > 6) {
|
|
5
|
+
throw new Error(`colorIndex must be an integer in [1, 6], got ${colorIndex}.`);
|
|
6
|
+
}
|
|
7
|
+
return `var(--niceeval-color-series-${colorIndex})`;
|
|
8
|
+
}
|
|
9
|
+
/** SVG pattern id:niceeval-series-pat-v{2|3|4}-c{1..6}。variant 1 是实心,无 pattern。 */
|
|
10
|
+
export function seriesPatternId(colorIndex, variant) {
|
|
11
|
+
if (variant === 1) {
|
|
12
|
+
throw new Error("variant 1 is solid fill; it has no pattern id.");
|
|
13
|
+
}
|
|
14
|
+
return `niceeval-series-pat-v${variant}-c${colorIndex}`;
|
|
15
|
+
}
|
|
16
|
+
export function seriesFill(colorIndex, variant) {
|
|
17
|
+
if (variant === 1)
|
|
18
|
+
return seriesColorVar(colorIndex);
|
|
19
|
+
return `url(#${seriesPatternId(colorIndex, variant)})`;
|
|
20
|
+
}
|
|
21
|
+
/** 线型变体 → strokeDasharray;variant 1 为空串(实线)。 */
|
|
22
|
+
export function seriesStrokeDasharray(variant) {
|
|
23
|
+
switch (variant) {
|
|
24
|
+
case 1:
|
|
25
|
+
return "";
|
|
26
|
+
case 2:
|
|
27
|
+
return "6 4";
|
|
28
|
+
case 3:
|
|
29
|
+
return "2 3";
|
|
30
|
+
case 4:
|
|
31
|
+
return "8 3 2 3";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** 四种 marker 形状(viewBox 0 0 12 12),变体按 docs 槽序表 1–4。 */
|
|
35
|
+
export function seriesMarkerShape(variant) {
|
|
36
|
+
switch (variant) {
|
|
37
|
+
case 1:
|
|
38
|
+
// 圆
|
|
39
|
+
return { path: "M6 1.5a4.5 4.5 0 1 0 0.01 0z", viewBox: "0 0 12 12" };
|
|
40
|
+
case 2:
|
|
41
|
+
// 方
|
|
42
|
+
return { path: "M2.5 2.5h7v7h-7z", viewBox: "0 0 12 12" };
|
|
43
|
+
case 3:
|
|
44
|
+
// 菱
|
|
45
|
+
return { path: "M6 1.5 10.5 6 6 10.5 1.5 6z", viewBox: "0 0 12 12" };
|
|
46
|
+
case 4:
|
|
47
|
+
// 三角
|
|
48
|
+
return { path: "M6 1.5 10.5 10.5 1.5 10.5z", viewBox: "0 0 12 12" };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function seriesMarker(colorIndex, variant) {
|
|
52
|
+
const shape = seriesMarkerShape(variant);
|
|
53
|
+
const color = seriesColorVar(colorIndex);
|
|
54
|
+
return {
|
|
55
|
+
path: shape.path,
|
|
56
|
+
viewBox: shape.viewBox,
|
|
57
|
+
fill: color,
|
|
58
|
+
stroke: color,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 从 presentation.fill 反推 HTML 柱需要的系列 class。
|
|
63
|
+
* SVG 用 url(#pattern)/var();HTML 柱不能引用 SVG pattern,改挂 series-cN + fill-vN 类,
|
|
64
|
+
* 由 styles.css 用 repeating-linear-gradient 画等效图案,颜色仍走 --series 令牌。
|
|
65
|
+
*/
|
|
66
|
+
export function seriesClassesFromFill(fill) {
|
|
67
|
+
const pattern = /^url\(#niceeval-series-pat-v([2-4])-c([1-6])\)$/.exec(fill);
|
|
68
|
+
if (pattern) {
|
|
69
|
+
const variant = pattern[1];
|
|
70
|
+
const colorIndex = Number(pattern[2]);
|
|
71
|
+
return `niceeval-series-c${colorIndex - 1} niceeval-series-fill-v${variant}`;
|
|
72
|
+
}
|
|
73
|
+
const solid = /^var\(--niceeval-color-series-([1-6])\)$/.exec(fill);
|
|
74
|
+
if (solid) {
|
|
75
|
+
return `niceeval-series-c${Number(solid[1]) - 1}`;
|
|
76
|
+
}
|
|
77
|
+
return "niceeval-series-none";
|
|
78
|
+
}
|
|
79
|
+
/** 从 color 呈现或 series 的 stroke/fill 反推 series-cN 类(图例色点回落)。 */
|
|
80
|
+
export function seriesClassFromColorVar(color) {
|
|
81
|
+
const solid = /^var\(--niceeval-color-series-([1-6])\)$/.exec(color);
|
|
82
|
+
if (solid)
|
|
83
|
+
return `niceeval-series-c${Number(solid[1]) - 1}`;
|
|
84
|
+
return "niceeval-series-none";
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 页内注入一次的 SVG pattern defs。
|
|
88
|
+
* 18 个 pattern(3 非实心变体 × 6 色);id 与 seriesFill() 产出的 url(#…) 对齐。
|
|
89
|
+
* 子元素直接写 var(--niceeval-color-series-N),不走 currentColor
|
|
90
|
+
* (SVG pattern 内部 currentColor 取自 pattern 自身,引用者传不进来)。
|
|
91
|
+
*/
|
|
92
|
+
export function SeriesPatternDefs() {
|
|
93
|
+
const patterns = [];
|
|
94
|
+
for (let colorIndex = 1; colorIndex <= 6; colorIndex++) {
|
|
95
|
+
const color = seriesColorVar(colorIndex);
|
|
96
|
+
// v2:对角斜线
|
|
97
|
+
patterns.push(_jsxs("pattern", { id: seriesPatternId(colorIndex, 2), patternUnits: "userSpaceOnUse", width: "6", height: "6", children: [_jsx("rect", { width: "6", height: "6", fill: color, fillOpacity: 0.22 }), _jsx("path", { d: "M-1 1l2-2M0 6l6-6M5 7l2-2", stroke: color, strokeWidth: 1.4, fill: "none" })] }, `v2-c${colorIndex}`));
|
|
98
|
+
// v3:水平条纹
|
|
99
|
+
patterns.push(_jsxs("pattern", { id: seriesPatternId(colorIndex, 3), patternUnits: "userSpaceOnUse", width: "6", height: "6", children: [_jsx("rect", { width: "6", height: "6", fill: color, fillOpacity: 0.18 }), _jsx("path", { d: "M0 1.5h6M0 4.5h6", stroke: color, strokeWidth: 1.5, fill: "none" })] }, `v3-c${colorIndex}`));
|
|
100
|
+
// v4:点阵
|
|
101
|
+
patterns.push(_jsxs("pattern", { id: seriesPatternId(colorIndex, 4), patternUnits: "userSpaceOnUse", width: "6", height: "6", children: [_jsx("rect", { width: "6", height: "6", fill: color, fillOpacity: 0.16 }), _jsx("circle", { cx: "2", cy: "2", r: "1.15", fill: color }), _jsx("circle", { cx: "5", cy: "5", r: "1.15", fill: color })] }, `v4-c${colorIndex}`));
|
|
102
|
+
}
|
|
103
|
+
return (_jsx("svg", { className: "niceeval-series-defs", width: 0, height: 0, "aria-hidden": "true", focusable: "false", style: { position: "absolute", width: 0, height: 0, overflow: "hidden" }, children: _jsx("defs", { children: patterns }) }));
|
|
104
|
+
}
|
|
@@ -6,10 +6,9 @@
|
|
|
6
6
|
// - 聚合前按身份键去重(dedupeAttempts;missing-startedAt 不去重、如实保留、不透出警告);
|
|
7
7
|
// - null ≠ 0:缺数据不编数,覆盖率经 samples/total 如实暴露;
|
|
8
8
|
// - core 中立:只认 Metric / Dimension 接口,不出现具体 agent 名的分支。
|
|
9
|
-
import { encodeAttemptLocator } from "../../../record/locator.js";
|
|
10
9
|
import { comparabilityConfigOf, deepEqualJson } from "../../../sample/index.js";
|
|
11
10
|
import { foldEvalVerdict } from "../../../shared/verdict.js";
|
|
12
|
-
import { collectItems, computeCell, evalIdOf, experimentIdOf, fullEvalKey, groupItems, historicalOf, locatorOf, resolveInput, } from "../../model/aggregate.js";
|
|
11
|
+
import { collectItems, computeCell, evalIdOf, experimentIdOf, fullEvalKey, groupItems, historicalOf, locatorOf, msSince, resolveInput, staleReferenceOf, } from "../../model/aggregate.js";
|
|
13
12
|
import { attemptCostUSD, costUSD, durationMs, examScore, passRate, tokens, totalScore } from "../../model/metrics.js";
|
|
14
13
|
import { compactAssertionSummary, primaryAssertionSummary, summaryText } from "../../../assertions/display.js";
|
|
15
14
|
import { firstLine } from "../../../util.js";
|
|
@@ -68,14 +67,10 @@ async function attemptListItemOf(item) {
|
|
|
68
67
|
costUSD: attemptCostUSD(result),
|
|
69
68
|
startedAt,
|
|
70
69
|
historical,
|
|
71
|
-
...(historical ? { staleSinceMs:
|
|
70
|
+
...(historical ? { staleSinceMs: msSince(startedAt) } : {}),
|
|
72
71
|
locator: locatorOf(item),
|
|
73
72
|
};
|
|
74
73
|
}
|
|
75
|
-
/** 一个 ISO 时刻距渲染时刻(`Date.now()`)的毫秒数,恒不小于 0。 */
|
|
76
|
-
function staleSinceMsOf(startedAtIso) {
|
|
77
|
-
return Math.max(0, Date.now() - Date.parse(startedAtIso));
|
|
78
|
-
}
|
|
79
74
|
/**
|
|
80
75
|
* 「只看新执行」开关在场的判据(docs/feature/reports/components/summaries/experiment-table.md
|
|
81
76
|
* 「只看新执行」):Sample 里既没有历史执行也没有过期结论时不画开关——一个永远不改变行集的
|
|
@@ -201,16 +196,9 @@ function staleReferencesFor(experimentId, missingEvalIds, historyAttempts, ancho
|
|
|
201
196
|
}
|
|
202
197
|
const out = {};
|
|
203
198
|
for (const [evalId, candidates] of candidatesByEval) {
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
continue; // 无时刻的 legacy 落盘算不出时距,不伪造参考
|
|
208
|
-
out[evalId] = {
|
|
209
|
-
locator: newest.locator ??
|
|
210
|
-
encodeAttemptLocator({ runId: newest.run.runId, evalId: newest.evalId, attempt: newest.result.attempt }),
|
|
211
|
-
verdict: newest.result.verdict,
|
|
212
|
-
staleSinceMs: staleSinceMsOf(startedAt),
|
|
213
|
-
};
|
|
199
|
+
const reference = staleReferenceOf(candidates);
|
|
200
|
+
if (reference)
|
|
201
|
+
out[evalId] = reference;
|
|
214
202
|
}
|
|
215
203
|
return out;
|
|
216
204
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { TableContent } from "../../definition/cell.ts";
|
|
2
2
|
import type { AttemptListItem, EvalListItem, ExperimentListItem } from "../../model/types.ts";
|
|
3
|
+
/** 覆盖构成副行的 key 前缀;测试与消费方靠它把这一行从 Eval / 组行里筛出去。 */
|
|
4
|
+
export declare const COVERAGE_ROW_PREFIX = "coverage:";
|
|
3
5
|
export declare function experimentListContent(items: readonly ExperimentListItem[]): TableContent;
|
|
4
6
|
export declare function evalListContent(items: readonly EvalListItem[]): TableContent;
|
|
5
7
|
export declare function attemptListContent(items: readonly AttemptListItem[]): TableContent;
|
|
@@ -23,7 +23,6 @@ const HEADER = {
|
|
|
23
23
|
verdict: localizedMessage("experimentList.status"),
|
|
24
24
|
result: localizedMessage("experimentList.result"),
|
|
25
25
|
score: localizedMessage("experimentList.totalScore"),
|
|
26
|
-
coverage: localizedMessage("experimentList.coverage"),
|
|
27
26
|
};
|
|
28
27
|
/** 原料 → 行 cells:列集外的原料丢掉,原料没覆盖的列显式填 notApplicable。 */
|
|
29
28
|
function projectCells(bag, columns) {
|
|
@@ -382,13 +381,33 @@ function coverageSegments(item) {
|
|
|
382
381
|
count: [fresh, historical, stale, notRun][i],
|
|
383
382
|
}));
|
|
384
383
|
}
|
|
385
|
-
/**
|
|
384
|
+
/** 覆盖构成副行的 key 前缀;测试与消费方靠它把这一行从 Eval / 组行里筛出去。 */
|
|
385
|
+
export const COVERAGE_ROW_PREFIX = "coverage:";
|
|
386
|
+
/**
|
|
387
|
+
* 覆盖构成副行(docs/feature/reports/components/summaries/experiment-table.md「覆盖构成」):
|
|
388
|
+
* experiment 行 subRows 的最后一条,把已知题按结论出身分成四段互斥的构成格,交给中立的
|
|
389
|
+
* `composition` 格——渲染在同一个 `record` 列位置(与 Eval / Attempt 行的判定构成、占位行的
|
|
390
|
+
* missing 格同一个槽位,三种形态各自对应不同的行语义,不是同一行的三种读法)。
|
|
391
|
+
* 它不是 Eval / 组行,没有身份(entity 是 notApplicable),不参与嵌套排序或收起判定。
|
|
392
|
+
*/
|
|
393
|
+
function coverageRow(item, view) {
|
|
394
|
+
const bag = {
|
|
395
|
+
entity: { kind: "notApplicable" },
|
|
396
|
+
record: { kind: "composition", segments: coverageSegments(item) },
|
|
397
|
+
};
|
|
398
|
+
return {
|
|
399
|
+
key: `${COVERAGE_ROW_PREFIX}${item.experimentId}`,
|
|
400
|
+
cells: projectCells(bag, view.columns),
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
/** experiment 的 evalRows + missingEvalIds → 递归嵌套的 subRows,末尾追加覆盖构成副行。 */
|
|
386
404
|
function experimentSubRows(item, view) {
|
|
387
405
|
const members = [
|
|
388
406
|
...item.evalRows.map((row) => ({ kind: "eval", row })),
|
|
389
407
|
...item.missingEvalIds.map((evalId) => ({ kind: "missing", evalId })),
|
|
390
408
|
];
|
|
391
|
-
|
|
409
|
+
const nested = nestLevel(members, "", "", item, view);
|
|
410
|
+
return members.length > 0 ? [...nested, coverageRow(item, view)] : nested;
|
|
392
411
|
}
|
|
393
412
|
function experimentRow(item, view) {
|
|
394
413
|
const bag = {
|
|
@@ -401,9 +420,6 @@ function experimentRow(item, view) {
|
|
|
401
420
|
tokens: measureCell(item.tokens),
|
|
402
421
|
costUSD: measureCell(item.costUSD),
|
|
403
422
|
record: verdictCell(item.evalVerdicts),
|
|
404
|
-
// 覆盖构成是 experiment 这一行独有的事实(experiment-table.md「覆盖构成」),Eval / Attempt /
|
|
405
|
-
// 路径段组行没有这一格——projectCells 按列集自动填 notApplicable,不额外分支。
|
|
406
|
-
coverage: { kind: "composition", segments: coverageSegments(item) },
|
|
407
423
|
};
|
|
408
424
|
return {
|
|
409
425
|
key: item.experimentId,
|
|
@@ -423,7 +439,6 @@ function experimentColumns(composition) {
|
|
|
423
439
|
{ key: "tokens", better: "lower", header: HEADER.tokens },
|
|
424
440
|
{ key: "costUSD", better: "lower", header: HEADER.costUSD },
|
|
425
441
|
{ key: "record", header: HEADER.record },
|
|
426
|
-
{ key: "coverage", header: HEADER.coverage },
|
|
427
442
|
];
|
|
428
443
|
}
|
|
429
444
|
/** Eval / Attempt 平铺表的列集(两张表同一份)。 */
|
|
@@ -25,6 +25,10 @@ export type Cell = {
|
|
|
25
25
|
readonly counts?: VerdictCounts;
|
|
26
26
|
/** 计票覆盖的 attempt 引用(有证据可下钻的计票格才携带,如稳定性矩阵)。 */
|
|
27
27
|
readonly refs?: readonly AttemptLocator[];
|
|
28
|
+
/** 单判定形态历史执行的距今毫秒数;新执行时省略,不伪造 0(与 locator 格同一条纪律)。 */
|
|
29
|
+
readonly staleSinceMs?: number;
|
|
30
|
+
/** 单判定形态省略判定词、只留判定符(如对照矩阵逐格只放得下一个符号的场景)。 */
|
|
31
|
+
readonly bare?: boolean;
|
|
28
32
|
} | {
|
|
29
33
|
readonly kind: "score";
|
|
30
34
|
readonly earned: number;
|
|
@@ -40,8 +40,12 @@ export function formatCellText(cell, locale) {
|
|
|
40
40
|
}
|
|
41
41
|
if (cell.verdict !== undefined) {
|
|
42
42
|
const v = cell.verdict === "skipped" ? "skipped" : cell.verdict;
|
|
43
|
+
const stale = cell.staleSinceMs !== undefined ? ` ${formatTimeDistance(cell.staleSinceMs, loc)}` : "";
|
|
43
44
|
// 判定符与判定词同场,与 locator 格、web 面同一条纪律:单色打印下照样读得出。
|
|
44
|
-
|
|
45
|
+
// bare 省略判定词,只留判定符(+ 可选时距),供逐格空间紧张的场景(如对照矩阵)使用。
|
|
46
|
+
if (cell.bare)
|
|
47
|
+
return `${verdictMark(v)}${stale}`;
|
|
48
|
+
return `${verdictMark(v)} ${localeText(loc, `verdict.${v}`)}${stale}`;
|
|
45
49
|
}
|
|
46
50
|
return "—";
|
|
47
51
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { SeriesPatternDefs, seriesClassFromColorVar, seriesClassesFromFill, } from "../../assets/series-encoding.js";
|
|
2
3
|
import { defineComponent } from "../tree.js";
|
|
3
4
|
import { countText, localeText, resolveLocalizedText } from "../../model/locale.js";
|
|
4
5
|
import { formatAxisTick, formatMetricValue, shortestUniqueLabels } from "../../model/format.js";
|
|
@@ -91,7 +92,8 @@ function chartFieldLabel(field, meta, locale) {
|
|
|
91
92
|
const base = dictionary[field] ? localeText(locale, dictionary[field]) : field;
|
|
92
93
|
return meta.unit ? `${base}(${meta.unit})` : base;
|
|
93
94
|
}
|
|
94
|
-
|
|
95
|
+
/** 解析点所属 series 句柄与下标;单系列隐式图不声明视觉身份。 */
|
|
96
|
+
function seriesPresentationOf(mapped, series, point, ctx) {
|
|
95
97
|
let handle;
|
|
96
98
|
let index;
|
|
97
99
|
if (series.byField !== undefined && point.seriesValue !== undefined) {
|
|
@@ -101,12 +103,104 @@ function seriesClass(mapped, series, point, ctx) {
|
|
|
101
103
|
else {
|
|
102
104
|
const ids = mapped.filter((item) => !item.hidden && item.byField === undefined).map((item) => item.id);
|
|
103
105
|
if (ids.length < 2)
|
|
104
|
-
return
|
|
106
|
+
return undefined;
|
|
105
107
|
handle = IMPLICIT_SERIES_HANDLE;
|
|
106
108
|
index = ids.indexOf(series.id);
|
|
107
109
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
+
if (index < 0)
|
|
111
|
+
return undefined;
|
|
112
|
+
return ctx.dimension(handle).at(index);
|
|
113
|
+
}
|
|
114
|
+
/** SVG 路径:只挂 series-cN 设 --series;pattern 由 fill 属性 / style 承载。 */
|
|
115
|
+
function seriesColorClass(presentation) {
|
|
116
|
+
if (!presentation)
|
|
117
|
+
return "niceeval-series-none";
|
|
118
|
+
if (presentation.kind === "color")
|
|
119
|
+
return seriesClassFromColorVar(presentation.color);
|
|
120
|
+
if (presentation.kind !== "series")
|
|
121
|
+
return "niceeval-series-none";
|
|
122
|
+
// switch 按 mark 收窄;Fill 的 mark 是 "bar"|"area" 联合,if/|| 在部分 TS 版本下剔不干净。
|
|
123
|
+
switch (presentation.mark) {
|
|
124
|
+
case "bar":
|
|
125
|
+
case "area": {
|
|
126
|
+
// 只要色类,不要 HTML 专用的 fill-vN(SVG 用 url(#pattern))。
|
|
127
|
+
const classes = seriesClassesFromFill(presentation.fill).split(" ");
|
|
128
|
+
return classes.find((c) => c.startsWith("niceeval-series-c") || c === "niceeval-series-none") ?? "niceeval-series-none";
|
|
129
|
+
}
|
|
130
|
+
case "line":
|
|
131
|
+
return seriesClassFromColorVar(presentation.stroke);
|
|
132
|
+
case "scatter":
|
|
133
|
+
return seriesClassFromColorVar(presentation.marker.fill);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** HTML 横向柱:色类 + fill-vN 图案类(CSS repeating-linear-gradient 等效 SVG pattern)。 */
|
|
137
|
+
function seriesHtmlBarClass(presentation) {
|
|
138
|
+
if (!presentation)
|
|
139
|
+
return "niceeval-series-none";
|
|
140
|
+
if (presentation.kind === "series" && (presentation.mark === "bar" || presentation.mark === "area")) {
|
|
141
|
+
return seriesClassesFromFill(presentation.fill);
|
|
142
|
+
}
|
|
143
|
+
return seriesColorClass(presentation);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 作者在 `<Series line>` 上显式声明的 dashed/dotted **优先于** 页级 variant 的 strokeDasharray。
|
|
147
|
+
* 未声明时消费 LineSeriesPresentation / FillSeriesPresentation 的 strokeDasharray(空串 = 实线)。
|
|
148
|
+
*/
|
|
149
|
+
function resolveStrokeDasharray(series, presentation) {
|
|
150
|
+
if (series.line !== undefined)
|
|
151
|
+
return lineDash(series.line);
|
|
152
|
+
if (presentation?.kind === "series" && "strokeDasharray" in presentation) {
|
|
153
|
+
return presentation.strokeDasharray || undefined;
|
|
154
|
+
}
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
/** SVG 柱/面:pattern fill 必须用 style 压过 `.niceeval-chart-bar { fill: var(--series) }`。 */
|
|
158
|
+
function seriesSvgFillStyle(presentation) {
|
|
159
|
+
if (presentation?.kind !== "series")
|
|
160
|
+
return undefined;
|
|
161
|
+
if (presentation.mark !== "bar" && presentation.mark !== "area")
|
|
162
|
+
return undefined;
|
|
163
|
+
if (!presentation.fill.startsWith("url("))
|
|
164
|
+
return undefined;
|
|
165
|
+
return { fill: presentation.fill };
|
|
166
|
+
}
|
|
167
|
+
function renderMarkerShape(presentation, px, py, colorClass, title) {
|
|
168
|
+
const marker = presentation?.kind === "series" && (presentation.mark === "scatter" || presentation.mark === "line")
|
|
169
|
+
? presentation.marker
|
|
170
|
+
: undefined;
|
|
171
|
+
if (!marker) {
|
|
172
|
+
return (_jsx("circle", { className: cx("niceeval-chart-dot", colorClass), cx: px, cy: py, r: 4.5, children: title }));
|
|
173
|
+
}
|
|
174
|
+
// path 在 0..12 viewBox;缩放到 ~9px 并居中到 (px, py)。
|
|
175
|
+
return (_jsx("path", { className: cx("niceeval-chart-dot", colorClass), d: marker.path, transform: `translate(${px} ${py}) scale(0.75) translate(-6 -6)`, fill: marker.fill, stroke: "var(--panel)", strokeWidth: 1.6, children: title }));
|
|
176
|
+
}
|
|
177
|
+
function legendSwatch(presentation, mark) {
|
|
178
|
+
if (!presentation || presentation.kind !== "series") {
|
|
179
|
+
return _jsx("span", { className: cx("niceeval-chart-legend-swatch", "niceeval-series-none") });
|
|
180
|
+
}
|
|
181
|
+
// 图例方块跟 series mark 取义:柱/面用填充图案,线用 dash+marker,散点用 marker 形状。
|
|
182
|
+
if (mark === "bar" || mark === "area") {
|
|
183
|
+
const fillClass = presentation.mark === "bar" || presentation.mark === "area"
|
|
184
|
+
? seriesClassesFromFill(presentation.fill)
|
|
185
|
+
: seriesColorClass(presentation);
|
|
186
|
+
return _jsx("span", { className: cx("niceeval-chart-legend-swatch", fillClass) });
|
|
187
|
+
}
|
|
188
|
+
if (mark === "line") {
|
|
189
|
+
// 线系图例需要 stroke + marker;presentation 可能是 line 或 scatter(by 切分后 mark 对齐)。
|
|
190
|
+
if (presentation.mark === "line") {
|
|
191
|
+
return (_jsxs("svg", { className: "niceeval-chart-legend-swatch-svg", width: "16", height: "10", "aria-hidden": "true", children: [_jsx("line", { x1: "0", y1: "5", x2: "16", y2: "5", stroke: presentation.stroke, strokeWidth: 2, strokeDasharray: presentation.strokeDasharray || undefined }), _jsx("path", { d: presentation.marker.path, transform: "translate(8 5) scale(0.55) translate(-6 -6)", fill: presentation.marker.fill })] }));
|
|
192
|
+
}
|
|
193
|
+
if (presentation.mark === "scatter") {
|
|
194
|
+
return (_jsxs("svg", { className: "niceeval-chart-legend-swatch-svg", width: "16", height: "10", "aria-hidden": "true", children: [_jsx("line", { x1: "0", y1: "5", x2: "16", y2: "5", stroke: presentation.marker.fill, strokeWidth: 2 }), _jsx("path", { d: presentation.marker.path, transform: "translate(8 5) scale(0.55) translate(-6 -6)", fill: presentation.marker.fill })] }));
|
|
195
|
+
}
|
|
196
|
+
// fill presentation 配 line mark 的退化:只画色块。
|
|
197
|
+
return _jsx("span", { className: cx("niceeval-chart-legend-swatch", seriesColorClass(presentation)) });
|
|
198
|
+
}
|
|
199
|
+
// mark === "scatter"
|
|
200
|
+
if (presentation.mark === "scatter" || presentation.mark === "line") {
|
|
201
|
+
return (_jsx("svg", { className: "niceeval-chart-legend-swatch-svg", width: "10", height: "10", "aria-hidden": "true", children: _jsx("path", { d: presentation.marker.path, transform: "translate(5 5) scale(0.7) translate(-6 -6)", fill: presentation.marker.fill }) }));
|
|
202
|
+
}
|
|
203
|
+
return _jsx("span", { className: cx("niceeval-chart-legend-swatch", seriesColorClass(presentation)) });
|
|
110
204
|
}
|
|
111
205
|
function metricDisplay(point, axis, meta, locale) {
|
|
112
206
|
if (meta.kind === "dimension") {
|
|
@@ -129,10 +223,7 @@ function renderLegend(mapped, visible, locale, ctx) {
|
|
|
129
223
|
: implicitIds.indexOf(series.id);
|
|
130
224
|
const presentation = handle ? ctx.dimension(handle).at(index) : undefined;
|
|
131
225
|
const label = presentation?.label ?? (series.label ? resolveLocalizedText(series.label, locale) : series.id);
|
|
132
|
-
|
|
133
|
-
? "niceeval-series-none"
|
|
134
|
-
: `niceeval-series-c${presentation.colorIndex - 1}`;
|
|
135
|
-
return (_jsx("li", { className: cx("niceeval-chart-legend-item", colorClass), children: label }, `${series.id}:${value}`));
|
|
226
|
+
return (_jsxs("li", { className: "niceeval-chart-legend-item", children: [legendSwatch(presentation, series.mark), label] }, `${series.id}:${value}`));
|
|
136
227
|
});
|
|
137
228
|
}) }));
|
|
138
229
|
}
|
|
@@ -142,12 +233,13 @@ function renderHorizontalBarsWeb(mapped, visible, axes, locale, ctx, options) {
|
|
|
142
233
|
const values = entries.map(({ point }) => point.y);
|
|
143
234
|
const boundMax = axes.yMeta.kind === "metric" ? axes.yMeta.bounds?.max : undefined;
|
|
144
235
|
const max = boundMax !== undefined && boundMax > 0 ? boundMax : Math.max(0, ...values);
|
|
145
|
-
return (_jsxs("figure", { className: cx("niceeval-report", "niceeval-chart", "niceeval-chart--bars-horizontal", options.className), children: [_jsx("div", { className: "niceeval-chart-bars-heading", children: chartFieldLabel(axes.yField, axes.yMeta, locale) }), _jsx("ol", { className: "niceeval-chart-bars", children: entries.map(({ series, point }) => {
|
|
236
|
+
return (_jsxs("figure", { className: cx("niceeval-report", "niceeval-chart", "niceeval-chart--bars-horizontal", options.className), children: [_jsx(SeriesPatternDefs, {}), _jsx("div", { className: "niceeval-chart-bars-heading", children: chartFieldLabel(axes.yField, axes.yMeta, locale) }), _jsx("ol", { className: "niceeval-chart-bars", children: entries.map(({ series, point }) => {
|
|
146
237
|
const rawLabel = point.xLabel ?? point.pointLabel;
|
|
147
238
|
const label = labels.get(rawLabel) ?? rawLabel;
|
|
148
239
|
const display = metricDisplay(point, "y", axes.yMeta, locale);
|
|
149
240
|
const href = pointHref(point, ctx, options.pointTarget);
|
|
150
|
-
const
|
|
241
|
+
const presentation = seriesPresentationOf(mapped, series, point, ctx);
|
|
242
|
+
const colorClass = seriesHtmlBarClass(presentation);
|
|
151
243
|
const ratio = max > 0 ? Math.max(0, Math.min(1, point.y / max)) : 0;
|
|
152
244
|
const value = (_jsxs("span", { className: "niceeval-chart-bar-value", children: [display, point.yCell && point.yCell.samples < point.yCell.total ? (_jsxs("sup", { children: [point.yCell.samples, "/", point.yCell.total] })) : null] }));
|
|
153
245
|
return (_jsxs("li", { className: "niceeval-chart-bar-row", children: [_jsx("span", { className: "niceeval-chart-bar-label", title: rawLabel, children: label }), _jsx("span", { className: "niceeval-chart-bar-track", children: _jsx("span", { className: cx("niceeval-chart-bar-fill", colorClass), style: { width: `${ratio * 100}%` }, title: `${rawLabel}\n${axes.yField}: ${display}` }) }), href ? _jsx("a", { className: "niceeval-locator", href: href, children: value }) : value] }, `${series.id}:${point.key}`));
|
|
@@ -203,16 +295,20 @@ function renderChartWeb(mapped, axes, locale, ctx, options) {
|
|
|
203
295
|
const labelByKey = shortestUniqueLabels(allPoints.map((p) => p.pointLabel));
|
|
204
296
|
const xLabel = chartFieldLabel(axes.xField, axes.xMeta, locale);
|
|
205
297
|
const yLabel = chartFieldLabel(axes.yField, axes.yMeta, locale);
|
|
206
|
-
const drawable = visible.flatMap((series) => series.points.map((point) =>
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
298
|
+
const drawable = visible.flatMap((series) => series.points.map((point) => {
|
|
299
|
+
const presentation = seriesPresentationOf(mapped, series, point, ctx);
|
|
300
|
+
return {
|
|
301
|
+
...point,
|
|
302
|
+
sourceSeriesId: series.id,
|
|
303
|
+
label: labelByKey.get(point.pointLabel) ?? point.pointLabel,
|
|
304
|
+
px: xScale.scale(point.x),
|
|
305
|
+
py: yScale.scale(point.y),
|
|
306
|
+
presentation,
|
|
307
|
+
seriesClass: seriesColorClass(presentation),
|
|
308
|
+
};
|
|
309
|
+
}));
|
|
214
310
|
const labels = placePointLabels(drawable.map((p) => ({ cx: p.px, cy: p.py, width: p.label.length * 6.4 + 10 })), { x0: 2, y0: 2, x1: WIDTH - 2, y1: HEIGHT - 2 });
|
|
215
|
-
return (_jsxs("figure", { className: cx("niceeval-report", "niceeval-chart", "niceeval-chart--scatter", options.className), children: [_jsxs("svg", { className: "niceeval-chart-svg", viewBox: `0 0 ${WIDTH} ${HEIGHT}`, role: "img", "aria-label": `${axes.xField} × ${axes.yField}`, children: [options.grid !== false ? (_jsxs("g", { className: "niceeval-chart-grid", children: [yScale.ticks.map((tick) => (_jsx("line", { x1: MARGIN.left, x2: MARGIN.left + PLOT_W, y1: yScale.scale(tick), y2: yScale.scale(tick) }, `gy${tick}`))), xScale.ticks.map((tick) => (_jsx("line", { y1: MARGIN.top, y2: MARGIN.top + PLOT_H, x1: xScale.scale(tick), x2: xScale.scale(tick) }, `gx${tick}`)))] })) : null, _jsx("g", { className: "niceeval-chart-axis niceeval-chart-axis-y", children: (axes.yMeta.kind === "dimension"
|
|
311
|
+
return (_jsxs("figure", { className: cx("niceeval-report", "niceeval-chart", "niceeval-chart--scatter", options.className), children: [_jsx(SeriesPatternDefs, {}), _jsxs("svg", { className: "niceeval-chart-svg", viewBox: `0 0 ${WIDTH} ${HEIGHT}`, role: "img", "aria-label": `${axes.xField} × ${axes.yField}`, children: [options.grid !== false ? (_jsxs("g", { className: "niceeval-chart-grid", children: [yScale.ticks.map((tick) => (_jsx("line", { x1: MARGIN.left, x2: MARGIN.left + PLOT_W, y1: yScale.scale(tick), y2: yScale.scale(tick) }, `gy${tick}`))), xScale.ticks.map((tick) => (_jsx("line", { y1: MARGIN.top, y2: MARGIN.top + PLOT_H, x1: xScale.scale(tick), x2: xScale.scale(tick) }, `gx${tick}`)))] })) : null, _jsx("g", { className: "niceeval-chart-axis niceeval-chart-axis-y", children: (axes.yMeta.kind === "dimension"
|
|
216
312
|
? [...new Set(drawable.map((point) => point.y))]
|
|
217
313
|
: yScale.ticks).map((tick) => (_jsx("text", { className: "niceeval-chart-tick", x: MARGIN.left - 8, y: yScale.scale(tick) + 3, textAnchor: "end", children: axes.yMeta.kind === "dimension"
|
|
218
314
|
? drawable.find((point) => point.y === tick)?.yLabel
|
|
@@ -226,7 +322,8 @@ function renderChartWeb(mapped, axes, locale, ctx, options) {
|
|
|
226
322
|
const seriesPoints = drawable.filter((p) => p.sourceSeriesId === series.id &&
|
|
227
323
|
(series.byField === undefined || p.seriesValue === value));
|
|
228
324
|
const ordered = series.connect ? [...seriesPoints].sort((a, b) => a.x - b.x) : seriesPoints;
|
|
229
|
-
const
|
|
325
|
+
const seriesClassName = ordered[0]?.seriesClass ?? "niceeval-series-none";
|
|
326
|
+
const seriesPresentation = ordered[0]?.presentation;
|
|
230
327
|
const baseline = yScale.scale(0);
|
|
231
328
|
const barGroups = [...new Set(visible
|
|
232
329
|
.filter((item) => item.mark === "bar")
|
|
@@ -235,11 +332,13 @@ function renderChartWeb(mapped, axes, locale, ctx, options) {
|
|
|
235
332
|
const groupIndex = Math.max(0, barGroups.indexOf(barGroup));
|
|
236
333
|
const totalBarWidth = Math.max(8, Math.min(48, PLOT_W / Math.max(1, allPoints.length)));
|
|
237
334
|
const barWidth = totalBarWidth / Math.max(1, barGroups.length);
|
|
238
|
-
|
|
335
|
+
const dash = resolveStrokeDasharray(series, seriesPresentation);
|
|
336
|
+
const areaFillStyle = seriesSvgFillStyle(seriesPresentation);
|
|
337
|
+
return (_jsxs("g", { className: cx("niceeval-chart-series", seriesClassName), "data-series": `${series.id}:${value}`, children: [series.mark === "area" && ordered.length > 1 ? (_jsx("polygon", { className: "niceeval-chart-area", style: areaFillStyle, points: [
|
|
239
338
|
`${ordered[0].px},${baseline}`,
|
|
240
339
|
...ordered.map((point) => `${point.px},${point.py}`),
|
|
241
340
|
`${ordered[ordered.length - 1].px},${baseline}`,
|
|
242
|
-
].join(" ") })) : null, (series.mark === "line" || series.mark === "area" || series.connect) && ordered.length > 1 ? (_jsx("polyline", { className: "niceeval-chart-line", points: ordered.map((p) => `${p.px},${p.py}`).join(" "), strokeDasharray:
|
|
341
|
+
].join(" ") })) : null, (series.mark === "line" || series.mark === "area" || series.connect) && ordered.length > 1 ? (_jsx("polyline", { className: "niceeval-chart-line", points: ordered.map((p) => `${p.px},${p.py}`).join(" "), strokeDasharray: dash })) : null, ordered.map((p) => {
|
|
243
342
|
const placed = labels[drawable.indexOf(p)];
|
|
244
343
|
const href = pointHref(p, ctx, options.pointTarget);
|
|
245
344
|
let shape;
|
|
@@ -247,10 +346,10 @@ function renderChartWeb(mapped, axes, locale, ctx, options) {
|
|
|
247
346
|
const baseValue = stackedBarBase(visible, series, p.x);
|
|
248
347
|
const baseY = yScale.scale(baseValue);
|
|
249
348
|
const topY = yScale.scale(baseValue + p.y);
|
|
250
|
-
shape = (_jsx("rect", { className: cx("niceeval-chart-bar", p.seriesClass), x: p.px - totalBarWidth / 2 + groupIndex * barWidth, y: Math.min(topY, baseY), width: barWidth, height: Math.max(1, Math.abs(baseY - topY)), children: _jsx("title", { children: `${p.pointLabel}\n${series.id}: ${metricDisplay(p, "y", axes.yMeta, locale)}` }) }));
|
|
349
|
+
shape = (_jsx("rect", { className: cx("niceeval-chart-bar", p.seriesClass), style: seriesSvgFillStyle(p.presentation), x: p.px - totalBarWidth / 2 + groupIndex * barWidth, y: Math.min(topY, baseY), width: barWidth, height: Math.max(1, Math.abs(baseY - topY)), children: _jsx("title", { children: `${p.pointLabel}\n${series.id}: ${metricDisplay(p, "y", axes.yMeta, locale)}` }) }));
|
|
251
350
|
}
|
|
252
351
|
else {
|
|
253
|
-
shape = (
|
|
352
|
+
shape = renderMarkerShape(p.presentation, p.px, p.py, p.seriesClass, _jsx("title", { children: `${p.pointLabel}\n${axes.xField}: ${metricDisplay(p, "x", axes.xMeta, locale)}\n${axes.yField}: ${metricDisplay(p, "y", axes.yMeta, locale)}` }));
|
|
254
353
|
}
|
|
255
354
|
return (_jsxs("g", { className: "niceeval-chart-point", children: [href ? _jsx("a", { href: href, children: shape }) : shape, series.mark === "scatter" && placed ? (_jsx("text", { className: "niceeval-chart-point-label", x: placed.x, y: placed.y, textAnchor: placed.anchor, children: p.label })) : null] }, p.key));
|
|
256
355
|
})] }, `${series.id}:${value}`));
|
|
@@ -480,7 +579,8 @@ export const Chart = defineComponent({
|
|
|
480
579
|
continue;
|
|
481
580
|
decls[spec.by] = {
|
|
482
581
|
dimension: spec.by,
|
|
483
|
-
|
|
582
|
+
// area 是 FillSeriesPresentation(填充图案),不再折叠成 line。
|
|
583
|
+
encoding: { kind: "series", mark: spec.mark },
|
|
484
584
|
values,
|
|
485
585
|
};
|
|
486
586
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { COMPONENT_RAW_CHILDREN, COMPONENT_ROLE, defineComponent, } from "./tree.js";
|
|
3
3
|
import { hrefForLocator } from "../components/shared.js";
|
|
4
4
|
import { localeText, resolveLocalizedText } from "../model/locale.js";
|
|
@@ -586,8 +586,9 @@ function renderCellWeb(cell, ctx) {
|
|
|
586
586
|
return (_jsxs("span", { className: "niceeval-verdict-tally", children: [parts.map((kind) => (_jsxs("span", { className: `niceeval-verdict-${kind}`, children: [cell.counts[kind], " ", localeText(ctx.locale, `verdict.${kind === "skipped" ? "skipped" : kind}`)] }, kind))), parts.length === 0 ? _jsx("span", { className: "niceeval-missing", children: MISSING_MARK }) : null] }));
|
|
587
587
|
}
|
|
588
588
|
const verdict = cell.verdict ?? "skipped";
|
|
589
|
+
const stale = cell.staleSinceMs !== undefined;
|
|
589
590
|
// 判定符走 verdictMark 单源,与 locator 格同一张表(errored 是 `!`,不并到 `✗`)。
|
|
590
|
-
return (_jsxs("span", { className:
|
|
591
|
+
return (_jsxs("span", { className: cx("niceeval-verdict", `niceeval-verdict-${verdict}`, stale ? "niceeval-stale" : undefined), title: stale ? localeText(ctx.locale, "experimentList.historicalTooltip") : undefined, children: [verdictMark(verdict === "skipped" ? "skipped" : verdict), !cell.bare ? _jsxs(_Fragment, { children: [" ", localeText(ctx.locale, `verdict.${verdict === "skipped" ? "skipped" : verdict}`)] }) : null, stale ? _jsx("span", { className: "niceeval-stale-distance", children: formatTimeDistance(cell.staleSinceMs, ctx.locale) }) : null] }));
|
|
591
592
|
}
|
|
592
593
|
case "metric":
|
|
593
594
|
return (_jsx(MetricCellView, { cell: cell.metric, href: ctx.showMeasureRefs === false ? undefined : ctx.href, locale: ctx.locale }));
|
|
@@ -92,8 +92,9 @@ export interface WebContext {
|
|
|
92
92
|
/** chrome 文案的 locale;官方组件渲染面经上下文读取,宿主外默认 "en"。 */
|
|
93
93
|
locale: ReportLocale;
|
|
94
94
|
/**
|
|
95
|
-
* 取本组件 `dimensions()` 声明的某个句柄在这一页的呈现面(
|
|
96
|
-
*
|
|
95
|
+
* 取本组件 `dimensions()` 声明的某个句柄在这一页的呈现面(label / color / series 的
|
|
96
|
+
* fill·stroke·marker 等可直接使用的值)。查别的组件的句柄或没声明的句柄抛
|
|
97
|
+
* `UndeclaredDimensionValueError`。
|
|
97
98
|
*/
|
|
98
99
|
dimension(handle: string): PresentedDimension;
|
|
99
100
|
}
|
|
@@ -152,6 +153,8 @@ export type ReportComponent<P extends object> = ((props: P) => ReactNode) & {
|
|
|
152
153
|
*/
|
|
153
154
|
export interface PageDimensions {
|
|
154
155
|
dimension(props: object, handle: string): PresentedDimension;
|
|
156
|
+
/** 维度 name → 值 → seriesSlot;测试与调试观察槽位分配,组件渲染不读槽号。 */
|
|
157
|
+
readonly slotsByDimension: ReadonlyMap<string, ReadonlyMap<string, number>>;
|
|
155
158
|
}
|
|
156
159
|
/** 宿主渲染前把这一页的分配结果挂到渲染上下文上(text 与 web 同一条通道)。 */
|
|
157
160
|
export declare function withPageDimensions<C extends object>(ctx: C, plan: PageDimensions | undefined): C;
|
|
@@ -142,6 +142,7 @@ export function collectPageDimensions(node, pins = {}, face = "web") {
|
|
|
142
142
|
visit(node);
|
|
143
143
|
const plan = allocatePageDimensions(handles, pins, { face });
|
|
144
144
|
return {
|
|
145
|
+
slotsByDimension: plan.slotsByDimension,
|
|
145
146
|
dimension(props, handle) {
|
|
146
147
|
const entry = byProps.get(props);
|
|
147
148
|
const key = entry?.keys.get(handle);
|
package/dist/report/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { aggregate, agent, costUSD, dedupeLocators, durationMs, evalId, evidence
|
|
|
2
2
|
export type { AggregateRow, AggregationSubject, Calculation, EvidenceRow, GroupFunction, MetricBasis, MetricFormat, MetricValue, Reducer, RollupOptions, } from "./model/calculation.ts";
|
|
3
3
|
export { toAttemptAssertions, toAttemptFacts, toAttemptFixPrompt, toAttemptListRows, toAttemptNotices, toAttemptRows, toAttemptSource, toAttemptSummary, toAttemptUsage, toConversationTurns, toDiffFiles, toEvalRows, toExperimentDetails, toExperimentRows, toHeroData, toRunNotices, toSampleFixPrompt, toSampleNotices, toSummaryItems, toTimelineNodes, toTraceNodes, } from "./model/conversions.ts";
|
|
4
4
|
export { presentDimension, shortestUniqueLabels } from "./presentation.ts";
|
|
5
|
-
export type { DimensionDeclaration, DimensionEncoding, PresentedDimension } from "./presentation.ts";
|
|
5
|
+
export type { ColorPresentation, DimensionDeclaration, DimensionEncoding, DimensionPresentation, FillSeriesPresentation, LabelPresentation, LineSeriesPresentation, PresentedDimension, ScatterSeriesPresentation, } from "./presentation.ts";
|
|
6
6
|
export { flag, label, numericFlag, numericLabel, numericRunConfig, runConfig } from "./model/flag.ts";
|
|
7
7
|
export { evaluationKindComposition } from "./model/evaluation-kind.ts";
|
|
8
8
|
export { annotatedSourceResult, attemptDetailsResult, comparisonResult, conversationResult, diffResult, historyResult, stabilityResult, standardOverviewResult, timingResult, usageResult, } from "./tasks.ts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AttemptHandle, SampleCoverage, SampleIssue, Run } from "../../record/types.ts";
|
|
2
2
|
import { type AttemptLocator } from "../../record/locator.ts";
|
|
3
|
-
import type { Aggregator, DimensionInput, AttemptMetric, MetricValue, MetricColumn, NumericAxis, ReportInput, SeriesInput } from "./types.ts";
|
|
3
|
+
import type { Aggregator, DimensionInput, AttemptMetric, MetricValue, MetricColumn, NumericAxis, ReportInput, SeriesInput, StaleConclusionReference } from "./types.ts";
|
|
4
4
|
import { type LocalizedText } from "./locale.ts";
|
|
5
5
|
import { evalPrefixPredicate } from "../../shared/aggregate.ts";
|
|
6
6
|
import type { JsonValue } from "../../shared/types.ts";
|
|
@@ -41,6 +41,15 @@ export declare function evalIdOf(item: Item): string;
|
|
|
41
41
|
* 来源可能有多个,水位基准是其中 startedAt 最新的一个。
|
|
42
42
|
*/
|
|
43
43
|
export declare function historicalOf(item: Item): boolean;
|
|
44
|
+
/** 一个 ISO 时刻距渲染时刻(`Date.now()`)的毫秒数,恒不小于 0。 */
|
|
45
|
+
export declare function msSince(iso: string): number;
|
|
46
|
+
/**
|
|
47
|
+
* 覆盖缺口 / 对照矩阵缺席格的「过期结论」参考(docs/feature/reports/components/summaries/
|
|
48
|
+
* experiment-table.md「覆盖缺口的两档占位行」、show/compare.md 同一套口径):候选已经是与
|
|
49
|
+
* 当前基准 configHash 不可比的那些,取其中最近一条;两个消费方(entity-lists 的占位行、
|
|
50
|
+
* DeltaTable 的缺席格)共用同一份「取最新」判据,不各自实现一遍。
|
|
51
|
+
*/
|
|
52
|
+
export declare function staleReferenceOf(candidates: readonly AttemptHandle[]): StaleConclusionReference | undefined;
|
|
44
53
|
/** 快照键:"<experimentId> @ <startedAt>"("run" 维度与手挑快照数组的对比用)。 */
|
|
45
54
|
export declare function snapshotKeyOf(run: Run): string;
|
|
46
55
|
/** 一组 Item 的 eval 全身份键:experimentId + eval id(聚合中的题级身份始终是这一对)。 */
|