niceeval 0.4.5 → 0.5.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/docs-site/zh/concepts/adapter.mdx +6 -6
- package/docs-site/zh/concepts/experiment.mdx +7 -7
- package/docs-site/zh/concepts/overview.mdx +1 -1
- package/docs-site/zh/concepts/tier.mdx +6 -6
- package/docs-site/zh/guides/agent-feedback-loop.mdx +63 -20
- package/docs-site/zh/guides/connect-your-agent.mdx +7 -7
- package/docs-site/zh/guides/custom-reports.mdx +243 -0
- package/docs-site/zh/guides/experiments.mdx +3 -3
- package/docs-site/zh/guides/report-components.mdx +252 -0
- package/docs-site/zh/guides/reporters.mdx +1 -1
- package/docs-site/zh/guides/results-data.mdx +224 -0
- package/docs-site/zh/guides/runner.mdx +1 -1
- package/docs-site/zh/guides/sandbox-agent.mdx +2 -2
- package/docs-site/zh/guides/viewing-results.mdx +147 -77
- package/docs-site/zh/guides/write-experiment.mdx +9 -9
- package/docs-site/zh/guides/write-send.mdx +6 -6
- package/docs-site/zh/index.mdx +1 -1
- package/docs-site/zh/introduction.mdx +1 -1
- package/docs-site/zh/reference/cli.mdx +10 -2
- package/docs-site/zh/reference/define-agent.mdx +5 -5
- package/docs-site/zh/reference/define-config.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +3 -3
- package/package.json +3 -2
- package/src/agents/ai-sdk.test.ts +1 -1
- package/src/agents/ai-sdk.ts +2 -2
- package/src/agents/bub.ts +14 -4
- package/src/agents/claude-code.ts +1 -1
- package/src/agents/codex.ts +2 -2
- package/src/agents/streaming.test.ts +1 -1
- package/src/agents/types.ts +4 -4
- package/src/agents/ui-message-stream.test.ts +1 -1
- package/src/cli.ts +104 -14
- package/src/context/context.test.ts +1 -1
- package/src/context/context.ts +3 -3
- package/src/context/session.ts +2 -2
- package/src/context/types.ts +2 -2
- package/src/i18n/en.ts +29 -7
- package/src/i18n/zh-CN.ts +24 -2
- package/src/report/aggregate.ts +79 -50
- package/src/report/components.tsx +190 -0
- package/src/report/compute.ts +149 -84
- package/src/report/default-report.tsx +222 -0
- package/src/report/dual-face.test.tsx +527 -0
- package/src/report/format.ts +29 -0
- package/src/report/index.ts +79 -24
- package/src/report/load.ts +67 -0
- package/src/report/metrics.ts +2 -1
- package/src/report/param.ts +18 -0
- package/src/report/primitives.tsx +117 -0
- package/src/report/react/CaseList.tsx +2 -2
- package/src/report/react/DeltaTable.tsx +2 -2
- package/src/report/react/MetricBars.tsx +109 -0
- package/src/report/react/MetricLine.tsx +200 -0
- package/src/report/react/MetricMatrix.tsx +1 -1
- package/src/report/react/MetricScatter.tsx +1 -1
- package/src/report/react/MetricTable.tsx +1 -1
- package/src/report/react/RunOverview.tsx +4 -4
- package/src/report/react/Scoreboard.tsx +3 -3
- package/src/report/react/cell.tsx +1 -1
- package/src/report/react/fixtures.ts +98 -36
- package/src/report/react/format.ts +3 -28
- package/src/report/react/index.tsx +41 -24
- package/src/report/react/render.test.tsx +8 -8
- package/src/report/react/styles.css +77 -0
- package/src/report/report.test.ts +248 -128
- package/src/report/report.ts +69 -0
- package/src/report/text/faces.ts +356 -0
- package/src/report/text/layout.ts +125 -0
- package/src/report/text/plot.ts +127 -0
- package/src/report/tree.ts +220 -0
- package/src/report/types.ts +75 -27
- package/src/report/web.ts +40 -0
- package/src/results/copy.ts +95 -41
- package/src/results/format.ts +12 -11
- package/src/results/index.ts +34 -17
- package/src/results/open.ts +201 -81
- package/src/results/results.test.ts +595 -191
- package/src/results/select.ts +107 -55
- package/src/results/types.ts +137 -45
- package/src/results/writer.ts +258 -0
- package/src/runner/attempt.ts +3 -3
- package/src/runner/fingerprint.ts +1 -1
- package/src/runner/reporters/artifacts.ts +38 -78
- package/src/runner/reporters/braintrust.test.ts +2 -2
- package/src/runner/reporters/braintrust.ts +3 -3
- package/src/runner/run.ts +1 -1
- package/src/runner/types.ts +18 -12
- package/src/show/compose.ts +215 -0
- package/src/show/index.ts +263 -0
- package/src/show/render.ts +456 -0
- package/src/show/show.test.ts +505 -0
- package/src/view/app/App.tsx +85 -24
- package/src/view/app/components/CostScoreChart.tsx +29 -10
- package/src/view/app/components/ExperimentTable.tsx +27 -9
- package/src/view/app/components/GroupSelector.tsx +1 -1
- package/src/view/app/i18n.ts +18 -3
- package/src/view/app/lib/attempt-route.test.ts +9 -9
- package/src/view/app/lib/attempt-route.ts +5 -5
- package/src/view/app/lib/outcome.ts +1 -3
- package/src/view/app/lib/rows.ts +95 -15
- package/src/view/app/main.tsx +18 -6
- package/src/view/app/pages/RunsPage.tsx +4 -7
- package/src/view/app/pages/TracesPage.tsx +3 -8
- package/src/view/app/types.ts +50 -6
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +20 -20
- package/src/view/data.test.ts +254 -0
- package/src/view/data.ts +336 -0
- package/src/view/index.ts +64 -10
- package/src/view/server.ts +31 -9
- package/src/view/shared/types.ts +47 -41
- package/src/view/styles.css +5 -0
- package/src/view/template.html +1 -0
- package/src/view/view-report.test.ts +308 -0
- package/docs-site/.mintignore +0 -13
- package/docs-site/AGENTS.md +0 -46
- package/docs-site/concepts/adapter.mdx +0 -287
- package/docs-site/concepts/assert.mdx +0 -243
- package/docs-site/concepts/drive.mdx +0 -118
- package/docs-site/concepts/evals.mdx +0 -108
- package/docs-site/concepts/experiment.mdx +0 -37
- package/docs-site/concepts/hitl.mdx +0 -83
- package/docs-site/concepts/judge.mdx +0 -129
- package/docs-site/concepts/overview.mdx +0 -98
- package/docs-site/concepts/tier.mdx +0 -42
- package/docs-site/docs-ref/00-index.md +0 -23
- package/docs-site/docs-ref/01-page-types.md +0 -43
- package/docs-site/docs-ref/03-style-rules.md +0 -45
- package/docs-site/docs-ref/04-code-examples.md +0 -37
- package/docs-site/docs-ref/05-dx-failure-paths.md +0 -45
- package/docs-site/docs-ref/06-checklists.md +0 -53
- package/docs-site/docs.json +0 -256
- package/docs-site/example/ai-agent-application.mdx +0 -148
- package/docs-site/example/claude-code-codex-plugin.mdx +0 -162
- package/docs-site/example/claude-code-codex-skill.mdx +0 -147
- package/docs-site/example/showcase.mdx +0 -39
- package/docs-site/example/tier1-ai-sdk-v7.mdx +0 -136
- package/docs-site/example/tier1-claude-sdk.mdx +0 -119
- package/docs-site/example/tier1-codex-sdk.mdx +0 -111
- package/docs-site/example/tier1-langgraph.mdx +0 -119
- package/docs-site/example/tier1-pi-sdk.mdx +0 -119
- package/docs-site/favicon.svg +0 -5
- package/docs-site/github-diff.css +0 -135
- package/docs-site/github-diff.js +0 -11
- package/docs-site/guides/authoring.mdx +0 -129
- package/docs-site/guides/ci-integration.mdx +0 -97
- package/docs-site/guides/connect-otel.mdx +0 -209
- package/docs-site/guides/connect-your-agent.mdx +0 -221
- package/docs-site/guides/dataset-fanout.mdx +0 -98
- package/docs-site/guides/experiments.mdx +0 -71
- package/docs-site/guides/fixtures.mdx +0 -147
- package/docs-site/guides/reporters.mdx +0 -113
- package/docs-site/guides/runner.mdx +0 -79
- package/docs-site/guides/sandbox-agent.mdx +0 -138
- package/docs-site/guides/sandbox-backends.mdx +0 -64
- package/docs-site/guides/scoring-guide.mdx +0 -92
- package/docs-site/guides/viewing-results.mdx +0 -195
- package/docs-site/guides/write-experiment.mdx +0 -81
- package/docs-site/guides/write-send.mdx +0 -347
- package/docs-site/index.mdx +0 -181
- package/docs-site/introduction.mdx +0 -141
- package/docs-site/quickstart.mdx +0 -136
- package/docs-site/reference/builtin-agents.mdx +0 -161
- package/docs-site/reference/capabilities.mdx +0 -76
- package/docs-site/reference/cli.mdx +0 -120
- package/docs-site/reference/define-agent.mdx +0 -168
- package/docs-site/reference/define-config.mdx +0 -41
- package/docs-site/reference/define-eval.mdx +0 -160
- package/docs-site/reference/events.mdx +0 -131
- package/docs-site/reference/expect.mdx +0 -112
- package/docs-site/tracker.js +0 -8
- package/src/report/react/data.ts +0 -17
- package/src/view/aggregate.ts +0 -103
- package/src/view/loader.test.ts +0 -61
- package/src/view/loader.ts +0 -257
package/src/report/aggregate.ts
CHANGED
|
@@ -4,75 +4,77 @@
|
|
|
4
4
|
// 和重试策略纠缠在一起(eval A=[1]、eval B=[0,0,0] 平铺 = 0.25,两级宏平均 = 0.5)。
|
|
5
5
|
// 自定义维度把同一道题的 attempt 分进不同组时,第一级折叠发生在各组内部。
|
|
6
6
|
|
|
7
|
-
import
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
dedupeAttempts,
|
|
9
|
+
type AttemptHandle,
|
|
10
|
+
type Selection,
|
|
11
|
+
type SelectionWarning,
|
|
12
|
+
type Snapshot,
|
|
13
|
+
} from "../results/index.ts";
|
|
9
14
|
import type {
|
|
10
15
|
Aggregator,
|
|
11
16
|
AttemptRef,
|
|
12
|
-
|
|
17
|
+
DimensionInput,
|
|
13
18
|
Metric,
|
|
14
19
|
MetricCell,
|
|
15
20
|
MetricColumn,
|
|
21
|
+
ParamRef,
|
|
16
22
|
} from "./types.ts";
|
|
17
23
|
import { formatMetricValue } from "./format.ts";
|
|
18
24
|
|
|
19
25
|
// 复合键分隔符:NUL 不会出现在 eval id / experimentId / ISO 时间里,拼接键不会串味
|
|
20
26
|
const KEY_SEP = "\u0000";
|
|
21
27
|
|
|
28
|
+
/** param 未声明时的组名:不猜,如实归一组。 */
|
|
29
|
+
export const PARAM_UNSET = "(unset)";
|
|
30
|
+
|
|
31
|
+
/** 计算函数的第一参:选集(warnings 随行)或手工挑的快照数组(没有挑选过程,自然无警告)。 */
|
|
32
|
+
export type SnapshotsInput = Selection | Snapshot[];
|
|
33
|
+
|
|
34
|
+
export function resolveInput(input: SnapshotsInput): {
|
|
35
|
+
snapshots: Snapshot[];
|
|
36
|
+
warnings: SelectionWarning[];
|
|
37
|
+
} {
|
|
38
|
+
if (Array.isArray(input)) return { snapshots: input, warnings: [] };
|
|
39
|
+
return { snapshots: input.snapshots, warnings: input.warnings };
|
|
40
|
+
}
|
|
41
|
+
|
|
22
42
|
/** 展平后的一条样本:attempt + 它所属的快照(维度解析与题级折叠都需要快照身份)。 */
|
|
23
43
|
export interface Item {
|
|
24
|
-
snapshot:
|
|
44
|
+
snapshot: Snapshot;
|
|
25
45
|
attempt: AttemptHandle;
|
|
26
46
|
}
|
|
27
47
|
|
|
28
48
|
export function experimentIdOf(item: Item): string {
|
|
29
|
-
return item.attempt.
|
|
49
|
+
return item.attempt.experimentId || item.snapshot.experimentId;
|
|
30
50
|
}
|
|
31
51
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return `${snapshot.experimentId}${KEY_SEP}${snapshot.startedAt}`;
|
|
52
|
+
export function evalIdOf(item: Item): string {
|
|
53
|
+
return item.attempt.evalId || item.attempt.result.id;
|
|
35
54
|
}
|
|
36
55
|
|
|
37
|
-
/**
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
return [
|
|
41
|
-
result.experimentId ?? snapshot.experimentId,
|
|
42
|
-
result.id,
|
|
43
|
-
result.attempt,
|
|
44
|
-
result.startedAt,
|
|
45
|
-
].join(KEY_SEP);
|
|
56
|
+
/** 快照键:与 view Compare 同口径的 "<experimentId> @ <startedAt>"。 */
|
|
57
|
+
export function snapshotKeyOf(snapshot: Snapshot): string {
|
|
58
|
+
return `${snapshot.experimentId} @ ${snapshot.startedAt}`;
|
|
46
59
|
}
|
|
47
60
|
|
|
48
61
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* TODO(results-lib):这是「去重是消费方义务」的本地实现(docs/results-lib.md「身份键与去重」);
|
|
53
|
-
* writer 给合入结果打标之后,按 niceeval/results 的规则收编,本函数随 handles.ts 一起换掉。
|
|
62
|
+
* 展平 + 聚合前去重(niceeval/results 的 dedupeAttempts,身份键
|
|
63
|
+
* (experimentId, evalId, attempt, startedAt))。missing-startedAt 的警告不透出:
|
|
64
|
+
* 官方产出永不缺 startedAt,缺失只可能来自 legacy 落盘,「不去重、如实保留重复」即终稿。
|
|
54
65
|
*/
|
|
55
|
-
export function
|
|
56
|
-
|
|
57
|
-
const
|
|
66
|
+
export function collectItems(snapshots: Snapshot[]): Item[] {
|
|
67
|
+
const snapshotByAttempt = new Map<AttemptHandle, Snapshot>();
|
|
68
|
+
const flattened: AttemptHandle[] = [];
|
|
58
69
|
for (const snapshot of snapshots) {
|
|
59
70
|
for (const attempt of snapshot.attempts) {
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
const prev = winner.get(key);
|
|
64
|
-
if (!prev || runStartedAt >= prev.runStartedAt) winner.set(key, { attempt, runStartedAt });
|
|
71
|
+
// 同一 handle 对象出现在两个快照里时以首次归属为准(手工挑重叠快照的极端场景)
|
|
72
|
+
if (!snapshotByAttempt.has(attempt)) snapshotByAttempt.set(attempt, snapshot);
|
|
73
|
+
flattened.push(attempt);
|
|
65
74
|
}
|
|
66
75
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
for (const snapshot of snapshots) {
|
|
70
|
-
for (const attempt of snapshot.attempts) {
|
|
71
|
-
const key = identityKey(snapshot, attempt.result);
|
|
72
|
-
if (key === null || winner.get(key)?.attempt === attempt) items.push({ snapshot, attempt });
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return items;
|
|
76
|
+
const { attempts } = dedupeAttempts(flattened);
|
|
77
|
+
return attempts.map((attempt) => ({ attempt, snapshot: snapshotByAttempt.get(attempt)! }));
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
/**
|
|
@@ -88,12 +90,12 @@ export function evalPrefixPredicate(evals?: string | string[]): (id: string) =>
|
|
|
88
90
|
export function filterItems(items: Item[], evals?: string | string[]): Item[] {
|
|
89
91
|
if (evals === undefined) return items;
|
|
90
92
|
const match = evalPrefixPredicate(evals);
|
|
91
|
-
return items.filter((item) => match(item
|
|
93
|
+
return items.filter((item) => match(evalIdOf(item)));
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
// ───────────────────────── 维度 ─────────────────────────
|
|
95
97
|
|
|
96
|
-
export function dimensionName(dimension:
|
|
98
|
+
export function dimensionName(dimension: DimensionInput): string {
|
|
97
99
|
return typeof dimension === "string" ? dimension : dimension.name;
|
|
98
100
|
}
|
|
99
101
|
|
|
@@ -103,8 +105,35 @@ export function evalGroupOf(id: string): string {
|
|
|
103
105
|
return slash === -1 ? id : id.slice(0, slash);
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
function isParamRef(dimension: DimensionInput): dimension is ParamRef {
|
|
109
|
+
return typeof dimension === "object" && "kind" in dimension && dimension.kind === "param";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** experiment 声明的 params(经 runner 原样透传进持久化字段 ExperimentRunInfo.params)。 */
|
|
113
|
+
function paramsOf(attempt: AttemptHandle): Record<string, unknown> | undefined {
|
|
114
|
+
return attempt.result.experiment?.params;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** param 声明值 → 组标签:label 函数优先,其余 String();未声明 → PARAM_UNSET。 */
|
|
118
|
+
export function paramGroupKey(ref: ParamRef, item: Item): string {
|
|
119
|
+
const value = paramsOf(item.attempt)?.[ref.name];
|
|
120
|
+
if (value === undefined) return PARAM_UNSET;
|
|
121
|
+
// 持久化字段是 Record<string, unknown>;声明侧的合法值就是这三种标量
|
|
122
|
+
if (typeof ref.label === "function") return ref.label(value as string | number | boolean);
|
|
123
|
+
return String(value);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** param 作轴:要求数值;未声明或非数值 → null(点不画,注脚报数)。 */
|
|
127
|
+
export function paramAxisValue(ref: ParamRef, item: Item): number | null {
|
|
128
|
+
const value = paramsOf(item.attempt)?.[ref.name];
|
|
129
|
+
return typeof value === "number" ? value : null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function dimensionKey(dimension: DimensionInput, item: Item): string {
|
|
133
|
+
if (typeof dimension !== "string") {
|
|
134
|
+
if (isParamRef(dimension)) return paramGroupKey(dimension, item);
|
|
135
|
+
return dimension.of(item.attempt);
|
|
136
|
+
}
|
|
108
137
|
const result = item.attempt.result;
|
|
109
138
|
switch (dimension) {
|
|
110
139
|
case "agent":
|
|
@@ -114,11 +143,11 @@ export function dimensionKey(dimension: Dimension, item: Item): string {
|
|
|
114
143
|
case "experiment":
|
|
115
144
|
return experimentIdOf(item);
|
|
116
145
|
case "eval":
|
|
117
|
-
return
|
|
146
|
+
return evalIdOf(item);
|
|
118
147
|
case "evalGroup":
|
|
119
|
-
return evalGroupOf(
|
|
148
|
+
return evalGroupOf(evalIdOf(item));
|
|
120
149
|
case "snapshot":
|
|
121
|
-
return
|
|
150
|
+
return snapshotKeyOf(item.snapshot);
|
|
122
151
|
default: {
|
|
123
152
|
// 穷尽检查:新增内置维度而漏改这里时编译期报错
|
|
124
153
|
const exhausted: never = dimension;
|
|
@@ -128,7 +157,7 @@ export function dimensionKey(dimension: Dimension, item: Item): string {
|
|
|
128
157
|
}
|
|
129
158
|
|
|
130
159
|
/** 按维度分组,保持首次出现顺序(无 sort 时表格行序即此序)。 */
|
|
131
|
-
export function groupItems(items: Item[], dimension:
|
|
160
|
+
export function groupItems(items: Item[], dimension: DimensionInput): Map<string, Item[]> {
|
|
132
161
|
const groups = new Map<string, Item[]>();
|
|
133
162
|
for (const item of items) {
|
|
134
163
|
const key = dimensionKey(dimension, item);
|
|
@@ -180,8 +209,8 @@ export async function computeCell(metric: Metric, items: Item[]): Promise<Metric
|
|
|
180
209
|
const value = await evaluateMetric(metric, item.attempt);
|
|
181
210
|
if (value === null) continue;
|
|
182
211
|
samples += 1;
|
|
183
|
-
refs.push(item.attempt.ref); //
|
|
184
|
-
const bucketKey = `${item
|
|
212
|
+
refs.push(item.attempt.ref); // 证据引用由句柄直供,不反查下标
|
|
213
|
+
const bucketKey = `${evalIdOf(item)}${KEY_SEP}${snapshotKeyOf(item.snapshot)}`;
|
|
185
214
|
const bucket = buckets.get(bucketKey);
|
|
186
215
|
if (bucket) bucket.push(value);
|
|
187
216
|
else buckets.set(bucketKey, [value]);
|
|
@@ -202,7 +231,7 @@ export function toColumn(metric: Metric): MetricColumn {
|
|
|
202
231
|
};
|
|
203
232
|
}
|
|
204
233
|
|
|
205
|
-
export function assertUniqueMetricNames(metrics: Metric[], where: string): void {
|
|
234
|
+
export function assertUniqueMetricNames(metrics: readonly Metric[], where: string): void {
|
|
206
235
|
const seen = new Set<string>();
|
|
207
236
|
for (const metric of metrics) {
|
|
208
237
|
if (seen.has(metric.name)) {
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// 官方双面组件的装配点:web 面(./react/ 的纯 React 组件)+ text 面(./text/faces.ts)
|
|
2
|
+
// + 挂在组件上的 data 计算函数。faces 两键必填 —— 配对是结构义务,不是配对表;
|
|
3
|
+
// MetricBars.data 就是 MetricMatrix.data(同一份矩阵数据的另一种摆法),别名显式化。
|
|
4
|
+
//
|
|
5
|
+
// 官方组件在宿主里自动接上证据室:web 面的 attemptHref 缺省取 ctx.attemptHref
|
|
6
|
+
// (宿主注入的证据室深链);显式传 prop 可覆盖(嵌进自己应用时自定去处)。
|
|
7
|
+
|
|
8
|
+
import { defineComponent, isHostWebContextActive } from "./tree.ts";
|
|
9
|
+
import type { AttemptRef } from "../results/index.ts";
|
|
10
|
+
import type {
|
|
11
|
+
CaseListData,
|
|
12
|
+
DeltaData,
|
|
13
|
+
LineData,
|
|
14
|
+
MatrixData,
|
|
15
|
+
OverviewData,
|
|
16
|
+
ScatterData,
|
|
17
|
+
ScoreboardData,
|
|
18
|
+
TableData,
|
|
19
|
+
} from "./types.ts";
|
|
20
|
+
import {
|
|
21
|
+
caseListData,
|
|
22
|
+
deltaData,
|
|
23
|
+
lineData,
|
|
24
|
+
matrixData,
|
|
25
|
+
overviewData,
|
|
26
|
+
scatterData,
|
|
27
|
+
scoreboardData,
|
|
28
|
+
tableData,
|
|
29
|
+
} from "./compute.ts";
|
|
30
|
+
import {
|
|
31
|
+
barsText,
|
|
32
|
+
caseListText,
|
|
33
|
+
deltaText,
|
|
34
|
+
lineText,
|
|
35
|
+
matrixText,
|
|
36
|
+
overviewText,
|
|
37
|
+
scatterText,
|
|
38
|
+
scoreboardText,
|
|
39
|
+
tableText,
|
|
40
|
+
} from "./text/faces.ts";
|
|
41
|
+
import { RunOverview as RunOverviewWeb } from "./react/RunOverview.tsx";
|
|
42
|
+
import { MetricTable as MetricTableWeb } from "./react/MetricTable.tsx";
|
|
43
|
+
import { MetricMatrix as MetricMatrixWeb } from "./react/MetricMatrix.tsx";
|
|
44
|
+
import { MetricBars as MetricBarsWeb } from "./react/MetricBars.tsx";
|
|
45
|
+
import { Scoreboard as ScoreboardWeb } from "./react/Scoreboard.tsx";
|
|
46
|
+
import { MetricScatter as MetricScatterWeb } from "./react/MetricScatter.tsx";
|
|
47
|
+
import { MetricLine as MetricLineWeb } from "./react/MetricLine.tsx";
|
|
48
|
+
import { DeltaTable as DeltaTableWeb } from "./react/DeltaTable.tsx";
|
|
49
|
+
import { CaseList as CaseListWeb } from "./react/CaseList.tsx";
|
|
50
|
+
|
|
51
|
+
// ───────────────────────── props ─────────────────────────
|
|
52
|
+
|
|
53
|
+
export interface RunOverviewProps {
|
|
54
|
+
data: OverviewData;
|
|
55
|
+
className?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface MetricTableProps {
|
|
59
|
+
data: TableData;
|
|
60
|
+
/** 传了,格子可点、下钻去处你定;不传,宿主里走证据室深链,宿主外纯展示。 */
|
|
61
|
+
attemptHref?: (ref: AttemptRef) => string;
|
|
62
|
+
className?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface MetricMatrixProps {
|
|
66
|
+
data: MatrixData;
|
|
67
|
+
attemptHref?: (ref: AttemptRef) => string;
|
|
68
|
+
className?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ScoreboardProps {
|
|
72
|
+
data: ScoreboardData;
|
|
73
|
+
className?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface MetricScatterProps {
|
|
77
|
+
data: ScatterData;
|
|
78
|
+
/** 点一个点 → 该配置的下钻页。 */
|
|
79
|
+
pointHref?: (row: ScatterData["rows"][number]) => string;
|
|
80
|
+
className?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface MetricLineProps {
|
|
84
|
+
data: LineData;
|
|
85
|
+
pointHref?: (row: LineData["rows"][number]) => string;
|
|
86
|
+
className?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface DeltaTableProps {
|
|
90
|
+
data: DeltaData;
|
|
91
|
+
className?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface CaseListProps {
|
|
95
|
+
data: CaseListData;
|
|
96
|
+
attemptHref?: (ref: AttemptRef) => string;
|
|
97
|
+
className?: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ───────────────────────── 装配 ─────────────────────────
|
|
101
|
+
|
|
102
|
+
/** 页头 KPI 条:何时跑的、几个配置、几道题、通过率、总成本;选集的警告随行显示在条内。 */
|
|
103
|
+
export const RunOverview = Object.assign(
|
|
104
|
+
defineComponent<RunOverviewProps>({
|
|
105
|
+
web: (props) => <RunOverviewWeb {...props} />,
|
|
106
|
+
text: ({ data }) => overviewText(data),
|
|
107
|
+
}),
|
|
108
|
+
{ data: overviewData },
|
|
109
|
+
);
|
|
110
|
+
RunOverview.displayName = "RunOverview";
|
|
111
|
+
|
|
112
|
+
/** 榜单:一行一个维度值、一列一个指标,回答「谁整体更好」。 */
|
|
113
|
+
export const MetricTable = Object.assign(
|
|
114
|
+
defineComponent<MetricTableProps>({
|
|
115
|
+
web: (props, ctx) => <MetricTableWeb {...props} attemptHref={props.attemptHref ?? (isHostWebContextActive() ? ctx.attemptHref : undefined)} />,
|
|
116
|
+
text: ({ data }) => tableText(data),
|
|
117
|
+
}),
|
|
118
|
+
{ data: tableData },
|
|
119
|
+
);
|
|
120
|
+
MetricTable.displayName = "MetricTable";
|
|
121
|
+
|
|
122
|
+
/** 逐题格子:行 × 列两个维度、格子里一个指标,回答「哪道题谁挂了」。 */
|
|
123
|
+
export const MetricMatrix = Object.assign(
|
|
124
|
+
defineComponent<MetricMatrixProps>({
|
|
125
|
+
web: (props, ctx) => <MetricMatrixWeb {...props} attemptHref={props.attemptHref ?? (isHostWebContextActive() ? ctx.attemptHref : undefined)} />,
|
|
126
|
+
text: ({ data }) => matrixText(data),
|
|
127
|
+
}),
|
|
128
|
+
{ data: matrixData },
|
|
129
|
+
);
|
|
130
|
+
MetricMatrix.displayName = "MetricMatrix";
|
|
131
|
+
|
|
132
|
+
/** 分组条形:同一份矩阵数据的另一种摆法;MetricBars.data 就是 MetricMatrix.data 的别名。 */
|
|
133
|
+
export const MetricBars = Object.assign(
|
|
134
|
+
defineComponent<MetricMatrixProps>({
|
|
135
|
+
web: (props, ctx) => <MetricBarsWeb {...props} attemptHref={props.attemptHref ?? (isHostWebContextActive() ? ctx.attemptHref : undefined)} />,
|
|
136
|
+
text: ({ data }) => barsText(data),
|
|
137
|
+
}),
|
|
138
|
+
{ data: matrixData },
|
|
139
|
+
);
|
|
140
|
+
MetricBars.displayName = "MetricBars";
|
|
141
|
+
|
|
142
|
+
/** 考试成绩单:总分 + 分科小计,固定分母、missing 如实报。 */
|
|
143
|
+
export const Scoreboard = Object.assign(
|
|
144
|
+
defineComponent<ScoreboardProps>({
|
|
145
|
+
web: (props) => <ScoreboardWeb {...props} />,
|
|
146
|
+
text: ({ data }) => scoreboardText(data),
|
|
147
|
+
}),
|
|
148
|
+
{ data: scoreboardData },
|
|
149
|
+
);
|
|
150
|
+
Scoreboard.displayName = "Scoreboard";
|
|
151
|
+
|
|
152
|
+
/** 质量 × 成本 frontier:每个点一个配置、两个指标各占一轴,「好」的角落恒在右上。 */
|
|
153
|
+
export const MetricScatter = Object.assign(
|
|
154
|
+
defineComponent<MetricScatterProps>({
|
|
155
|
+
web: (props) => <MetricScatterWeb {...props} />,
|
|
156
|
+
text: ({ data }, ctx) => scatterText(data, ctx),
|
|
157
|
+
}),
|
|
158
|
+
{ data: scatterData },
|
|
159
|
+
);
|
|
160
|
+
MetricScatter.displayName = "MetricScatter";
|
|
161
|
+
|
|
162
|
+
/** 趋势线:x 是 experiment 声明的 param(param()),同系列按 x 排序连线。 */
|
|
163
|
+
export const MetricLine = Object.assign(
|
|
164
|
+
defineComponent<MetricLineProps>({
|
|
165
|
+
web: (props) => <MetricLineWeb {...props} />,
|
|
166
|
+
text: ({ data }, ctx) => lineText(data, ctx),
|
|
167
|
+
}),
|
|
168
|
+
{ data: lineData },
|
|
169
|
+
);
|
|
170
|
+
MetricLine.displayName = "MetricLine";
|
|
171
|
+
|
|
172
|
+
/** 成对对比:每行一对配置、格子里 A、B、Δ 三个值,涨跌好坏由 better 判定。 */
|
|
173
|
+
export const DeltaTable = Object.assign(
|
|
174
|
+
defineComponent<DeltaTableProps>({
|
|
175
|
+
web: (props) => <DeltaTableWeb {...props} />,
|
|
176
|
+
text: ({ data }) => deltaText(data),
|
|
177
|
+
}),
|
|
178
|
+
{ data: deltaData },
|
|
179
|
+
);
|
|
180
|
+
DeltaTable.displayName = "DeltaTable";
|
|
181
|
+
|
|
182
|
+
/** 失败案例清单:榜单回答「多少」,它回答「为什么」;truncated 如实报剩余。 */
|
|
183
|
+
export const CaseList = Object.assign(
|
|
184
|
+
defineComponent<CaseListProps>({
|
|
185
|
+
web: (props, ctx) => <CaseListWeb {...props} attemptHref={props.attemptHref ?? (isHostWebContextActive() ? ctx.attemptHref : undefined)} />,
|
|
186
|
+
text: (props, ctx) => caseListText(props.data, ctx),
|
|
187
|
+
}),
|
|
188
|
+
{ data: caseListData },
|
|
189
|
+
);
|
|
190
|
+
CaseList.displayName = "CaseList";
|