niceeval 0.6.2 → 0.7.1
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 +23 -23
- package/README.zh.md +6 -6
- package/dist/agents/types.d.ts +2 -2
- package/dist/i18n/zh-CN.d.ts +3 -3
- package/dist/report/aggregate.d.ts +32 -26
- package/dist/report/aggregate.js +157 -76
- package/dist/report/built-in/index.d.ts +2 -0
- package/dist/report/built-in/index.js +8 -0
- package/dist/report/components.d.ts +91 -164
- package/dist/report/components.js +377 -114
- package/dist/report/compute.d.ts +86 -73
- package/dist/report/compute.js +592 -432
- package/dist/report/flag.d.ts +28 -17
- package/dist/report/flag.js +86 -16
- package/dist/report/format.d.ts +11 -11
- package/dist/report/format.js +17 -15
- package/dist/report/index.d.ts +16 -17
- package/dist/report/index.js +20 -22
- package/dist/report/load.js +3 -2
- package/dist/report/locale.d.ts +49 -34
- package/dist/report/locale.js +106 -58
- package/dist/report/metrics.d.ts +10 -3
- package/dist/report/metrics.js +46 -12
- package/dist/report/primitives.d.ts +42 -15
- package/dist/report/primitives.js +135 -26
- package/dist/report/react/AttemptList.d.ts +10 -8
- package/dist/report/react/AttemptList.js +18 -10
- package/dist/report/react/DeltaTable.js +19 -18
- package/dist/report/react/EvalList.d.ts +3 -3
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentComparison.d.ts +4 -2
- package/dist/report/react/ExperimentComparison.js +5 -4
- package/dist/report/react/ExperimentList.d.ts +3 -3
- package/dist/report/react/ExperimentList.js +16 -15
- package/dist/report/react/MetricBars.js +5 -4
- package/dist/report/react/MetricLine.js +12 -5
- package/dist/report/react/MetricMatrix.js +1 -1
- package/dist/report/react/MetricScatter.js +54 -17
- package/dist/report/react/MetricTable.js +2 -12
- package/dist/report/react/ScopeSummary.d.ts +10 -0
- package/dist/report/react/ScopeSummary.js +17 -0
- package/dist/report/react/Scoreboard.js +6 -6
- package/dist/report/react/cell.js +2 -2
- package/dist/report/react/fixtures.d.ts +5 -9
- package/dist/report/react/fixtures.js +105 -149
- package/dist/report/react/index.d.ts +15 -5
- package/dist/report/react/index.js +18 -7
- package/dist/report/report.d.ts +137 -20
- package/dist/report/report.js +261 -34
- package/dist/report/text/faces.d.ts +17 -19
- package/dist/report/text/faces.js +225 -157
- package/dist/report/text/plot.js +1 -1
- package/dist/report/text/table.js +2 -2
- package/dist/report/tree.d.ts +90 -40
- package/dist/report/tree.js +252 -94
- package/dist/report/types.d.ts +245 -300
- package/dist/report/types.js +4 -3
- package/dist/report/web.d.ts +21 -5
- package/dist/report/web.js +42 -16
- package/dist/results/select.d.ts +38 -16
- package/dist/results/select.js +73 -25
- package/dist/results/types.d.ts +38 -14
- package/dist/shared/aggregate.d.ts +3 -2
- package/dist/shared/aggregate.js +5 -4
- package/docs-site/zh/README.md +44 -0
- package/docs-site/zh/examples/ai-agent-application.mdx +63 -0
- package/docs-site/zh/examples/coding-agent-extensions.mdx +57 -0
- package/docs-site/zh/examples/index.mdx +50 -0
- package/docs-site/zh/{concepts → explanation}/adapter.mdx +11 -11
- package/docs-site/zh/{concepts → explanation}/assert.mdx +7 -7
- package/docs-site/zh/{concepts → explanation}/drive.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/evals.mdx +4 -4
- package/docs-site/zh/{concepts → explanation}/experiment.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/hitl.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/judge.mdx +5 -5
- package/docs-site/zh/{concepts → explanation}/overview.mdx +5 -5
- package/docs-site/zh/{guides → explanation}/runner.mdx +1 -1
- package/docs-site/zh/{concepts → explanation}/tier.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/agent-feedback-loop.mdx +7 -7
- package/docs-site/zh/{guides → how-to}/authoring.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/connect-otel.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/connect-your-agent.mdx +18 -18
- package/docs-site/zh/{guides → how-to}/custom-reports.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/experiments.mdx +3 -3
- package/docs-site/zh/{guides → how-to}/publish-report.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/sandbox-agent.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/sandbox-providers.mdx +1 -1
- package/docs-site/zh/{guides → how-to}/viewing-results.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/write-experiment.mdx +3 -3
- package/docs-site/zh/{guides → how-to}/write-send.mdx +13 -13
- package/docs-site/zh/index.mdx +23 -25
- package/docs-site/zh/introduction.mdx +8 -8
- package/docs-site/zh/reference/builtin-agents.mdx +5 -5
- package/docs-site/zh/reference/capabilities.mdx +6 -6
- package/docs-site/zh/reference/cli.mdx +9 -7
- package/docs-site/zh/reference/define-agent.mdx +1 -1
- package/docs-site/zh/reference/events.mdx +3 -3
- package/docs-site/zh/{guides → reference}/official-adapters.mdx +7 -7
- package/docs-site/zh/{guides → reference}/report-components.mdx +5 -5
- package/docs-site/zh/{guides → reference}/results-data.mdx +5 -5
- package/docs-site/zh/{guides → troubleshooting}/debug-sandbox.mdx +2 -2
- package/docs-site/zh/{guides → troubleshooting}/debugging.mdx +4 -2
- package/docs-site/zh/{quickstart.mdx → tutorials/quickstart.mdx} +5 -17
- package/package.json +6 -2
- package/src/agents/index.ts +2 -2
- package/src/agents/openai-compat.ts +1 -1
- package/src/agents/streaming.ts +2 -2
- package/src/agents/types.ts +3 -3
- package/src/cli.ts +42 -23
- package/src/context/context.ts +1 -1
- package/src/context/session.test.ts +1 -1
- package/src/context/session.ts +1 -1
- package/src/i18n/en.ts +18 -16
- package/src/i18n/zh-CN.ts +16 -15
- package/src/report/aggregate.ts +175 -87
- package/src/report/built-in/index.tsx +9 -0
- package/src/report/components.tsx +625 -285
- package/src/report/compute.ts +717 -515
- package/src/report/dual-render.test.tsx +738 -1148
- package/src/report/flag.ts +97 -33
- package/src/report/format.ts +18 -22
- package/src/report/index.ts +113 -58
- package/src/report/load.ts +3 -2
- package/src/report/locale.ts +120 -69
- package/src/report/metrics.ts +42 -12
- package/src/report/primitives.tsx +190 -45
- package/src/report/react/AttemptList.tsx +32 -20
- package/src/report/react/DeltaTable.tsx +63 -45
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentComparison.tsx +12 -7
- package/src/report/react/ExperimentList.tsx +38 -26
- package/src/report/react/MetricBars.tsx +5 -4
- package/src/report/react/MetricLine.tsx +13 -8
- package/src/report/react/MetricMatrix.tsx +2 -2
- package/src/report/react/MetricScatter.tsx +74 -20
- package/src/report/react/MetricTable.tsx +4 -76
- package/src/report/react/ScopeSummary.tsx +86 -0
- package/src/report/react/Scoreboard.tsx +28 -10
- package/src/report/react/cell.tsx +2 -2
- package/src/report/react/enhance.js +57 -5
- package/src/report/react/fixtures.ts +109 -156
- package/src/report/react/index.tsx +24 -39
- package/src/report/react/render.test.tsx +139 -104
- package/src/report/react/styles.css +181 -91
- package/src/report/report.test.ts +761 -1031
- package/src/report/report.ts +425 -47
- package/src/report/text/faces.ts +257 -164
- package/src/report/text/plot.ts +1 -1
- package/src/report/text/table.ts +2 -2
- package/src/report/tree.ts +362 -104
- package/src/report/types.ts +257 -287
- package/src/report/web.ts +63 -20
- package/src/results/attempt-evidence.test.ts +4 -4
- package/src/results/attempt-evidence.ts +5 -5
- package/src/results/copy.ts +6 -6
- package/src/results/host-equivalence.test.ts +26 -14
- package/src/results/index.ts +10 -4
- package/src/results/open.ts +8 -4
- package/src/results/results.test.ts +4 -3
- package/src/results/select.ts +104 -34
- package/src/results/types.ts +36 -14
- package/src/runner/feedback/human.test.ts +1 -1
- package/src/runner/run.ts +1 -1
- package/src/sandbox/cli-commands.ts +2 -2
- package/src/scoring/judge.test.ts +1 -1
- package/src/shared/aggregate.ts +5 -4
- package/src/show/compose.ts +50 -67
- package/src/show/index.ts +107 -56
- package/src/show/render.ts +43 -27
- package/src/show/report-host.test.ts +188 -0
- package/src/show/report-host.ts +375 -0
- package/src/show/show.test.ts +86 -36
- package/src/view/app/App.test.tsx +69 -0
- package/src/view/app/App.tsx +144 -48
- package/src/view/app/components/AttemptModal.tsx +324 -63
- package/src/view/app/components/CodeView.tsx +10 -4
- package/src/view/app/i18n.ts +31 -17
- package/src/view/app/lib/artifact-url.ts +14 -3
- package/src/view/app/main.tsx +13 -8
- package/src/view/app/pages/{RunsPage.tsx → AttemptsPage.tsx} +6 -6
- package/src/view/app/types.ts +4 -1
- package/src/view/artifact-serving.test.ts +21 -1
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +6 -6
- package/src/view/data.test.ts +9 -3
- package/src/view/data.ts +145 -49
- package/src/view/index.ts +48 -44
- package/src/view/server.ts +35 -15
- package/src/view/shared/types.ts +34 -5
- package/src/view/styles.css +224 -0
- package/src/view/view-report.test.ts +161 -57
- package/dist/report/built-ins/experiment-comparison.d.ts +0 -39
- package/dist/report/built-ins/experiment-comparison.js +0 -119
- package/dist/report/built-ins/index.d.ts +0 -2
- package/dist/report/built-ins/index.js +0 -2
- package/dist/report/react/GroupSummary.d.ts +0 -8
- package/dist/report/react/GroupSummary.js +0 -8
- package/dist/report/react/RunOverview.d.ts +0 -8
- package/dist/report/react/RunOverview.js +0 -12
- package/docs-site/zh/example/ai-agent-application.mdx +0 -152
- package/docs-site/zh/example/claude-code-codex-plugin.mdx +0 -167
- package/docs-site/zh/example/claude-code-codex-skill.mdx +0 -152
- package/docs-site/zh/example/showcase.mdx +0 -39
- package/src/report/built-in-user-parity.test.tsx +0 -597
- package/src/report/built-ins/experiment-comparison.tsx +0 -179
- package/src/report/built-ins/index.ts +0 -7
- package/src/report/react/GroupSummary.tsx +0 -66
- package/src/report/react/RunOverview.tsx +0 -109
- /package/docs-site/zh/{example/tier1-ai-sdk-v7.mdx → examples/integrations/ai-sdk-v7.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-claude-sdk.mdx → examples/integrations/claude-sdk.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-codex-sdk.mdx → examples/integrations/codex-sdk.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-langgraph.mdx → examples/integrations/langgraph.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-pi-sdk.mdx → examples/integrations/pi-sdk.mdx} +0 -0
- /package/docs-site/zh/{guides → how-to}/ci-integration.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/dataset-fanout.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/fixtures.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/reporters.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/scoring-guide.mdx +0 -0
|
@@ -1,61 +1,79 @@
|
|
|
1
1
|
// 官方双面组件的装配点:web 面(./react/ 的纯 React 组件)+ text 面(./text/faces.ts)
|
|
2
|
-
// +
|
|
3
|
-
// MetricBars
|
|
2
|
+
// + resolve 解析面(spec 形态由管线代调配套 *Data 函数)。faces 两键必填 —— 配对是结构
|
|
3
|
+
// 义务;MetricBars 与 MetricMatrix 消费同一份矩阵数据(同一个 metricMatrixData)。
|
|
4
|
+
//
|
|
5
|
+
// props 双形态以 `data` 判别(docs/feature/reports/library/metric-views.md):
|
|
6
|
+
// spec 形态 = 计算选项平铺 + 可选 `input`(默认宿主注入的 Scope),管线在 resolve 阶段
|
|
7
|
+
// 代调同名 *Data,与手工计算严格等价;data 形态接收算好的可序列化数据,跳过取数。
|
|
8
|
+
// 同一组件同时给出 `data` 与 spec 字段按完整用户反馈报错,不静默取一边。
|
|
9
|
+
// 组件消费 `data` 时校验结构,不符合当前形状按完整用户反馈报错并提示可能的版本漂移。
|
|
4
10
|
//
|
|
5
11
|
// 官方组件在宿主里自动接上证据室:web 面的 attemptHref 缺省取 ctx.attemptHref
|
|
6
|
-
// (宿主注入的证据室深链);显式传 prop 可覆盖(嵌进自己应用时自定去处)
|
|
7
|
-
// (ExperimentList / EvalList / AttemptList)没有这个覆盖口子——它们的 locator 徽标恒经
|
|
8
|
-
// ctx.attemptHref / ctx.attemptCommand,不接受 attemptHref/attemptCommand prop(docs/feature/reports/library.md
|
|
9
|
-
// 「嵌入自己的 React 页面」的函数签名没有这个参数),证据室深链在这三个组件上不是可选行为。
|
|
12
|
+
// (宿主注入的证据室深链);显式传 prop 可覆盖(嵌进自己应用时自定去处)。
|
|
10
13
|
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
14
|
+
import type { ReactNode } from "react";
|
|
15
|
+
import {
|
|
16
|
+
defineComponent,
|
|
17
|
+
isHostWebContextActive,
|
|
18
|
+
memoFetchOf,
|
|
19
|
+
type ReportComponent,
|
|
20
|
+
type ResolveContext,
|
|
21
|
+
type TextContext,
|
|
22
|
+
type WebContext,
|
|
23
|
+
} from "./tree.ts";
|
|
13
24
|
import type { ReportLocale } from "./locale.ts";
|
|
14
25
|
import type { AttemptLocator } from "../results/locator.ts";
|
|
15
|
-
import type { Selection } from "../results/types.ts";
|
|
16
26
|
import type {
|
|
17
27
|
AttemptListItem,
|
|
18
28
|
DeltaData,
|
|
29
|
+
EntityListDataOptions,
|
|
19
30
|
EvalListItem,
|
|
31
|
+
ExperimentComparisonData,
|
|
20
32
|
ExperimentListItem,
|
|
21
|
-
GroupSummaryData,
|
|
22
33
|
LineData,
|
|
23
34
|
MatrixData,
|
|
24
|
-
|
|
35
|
+
ReportInput,
|
|
25
36
|
ScatterData,
|
|
37
|
+
ScopeSummaryData,
|
|
26
38
|
ScoreboardData,
|
|
27
39
|
TableData,
|
|
28
40
|
} from "./types.ts";
|
|
29
41
|
import {
|
|
30
42
|
attemptListData,
|
|
31
|
-
|
|
43
|
+
deltaTableData,
|
|
32
44
|
evalListData,
|
|
45
|
+
experimentComparisonData,
|
|
33
46
|
experimentListData,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
47
|
+
metricLineData,
|
|
48
|
+
metricMatrixData,
|
|
49
|
+
metricScatterData,
|
|
50
|
+
metricTableData,
|
|
51
|
+
scopeSummaryData,
|
|
39
52
|
scoreboardData,
|
|
40
|
-
|
|
53
|
+
type DeltaTableOptions,
|
|
54
|
+
type MetricLineOptions,
|
|
55
|
+
type MetricMatrixOptions,
|
|
56
|
+
type MetricScatterOptions,
|
|
57
|
+
type MetricTableOptions,
|
|
58
|
+
type ScoreboardOptions,
|
|
41
59
|
} from "./compute.ts";
|
|
42
|
-
import
|
|
60
|
+
import { collectItems, locatorOf, resolveInput } from "./aggregate.ts";
|
|
43
61
|
import {
|
|
44
62
|
attemptListText,
|
|
45
|
-
barsText,
|
|
46
63
|
deltaText,
|
|
47
64
|
evalListText,
|
|
65
|
+
experimentComparisonText,
|
|
48
66
|
experimentListText,
|
|
49
|
-
|
|
67
|
+
barsText,
|
|
50
68
|
lineText,
|
|
51
69
|
matrixText,
|
|
52
|
-
overviewText,
|
|
53
70
|
scatterText,
|
|
54
71
|
scoreboardText,
|
|
72
|
+
scopeSummaryText,
|
|
55
73
|
tableText,
|
|
56
74
|
} from "./text/faces.ts";
|
|
57
|
-
import {
|
|
58
|
-
import {
|
|
75
|
+
import { ScopeSummary as ScopeSummaryWeb } from "./react/ScopeSummary.tsx";
|
|
76
|
+
import { ExperimentComparisonView } from "./react/ExperimentComparison.tsx";
|
|
59
77
|
import { ExperimentList as ExperimentListWeb } from "./react/ExperimentList.tsx";
|
|
60
78
|
import { EvalList as EvalListWeb } from "./react/EvalList.tsx";
|
|
61
79
|
import { AttemptList as AttemptListWeb } from "./react/AttemptList.tsx";
|
|
@@ -67,305 +85,627 @@ import { MetricScatter as MetricScatterWeb } from "./react/MetricScatter.tsx";
|
|
|
67
85
|
import { MetricLine as MetricLineWeb } from "./react/MetricLine.tsx";
|
|
68
86
|
import { DeltaTable as DeltaTableWeb } from "./react/DeltaTable.tsx";
|
|
69
87
|
|
|
70
|
-
// ─────────────────────────
|
|
88
|
+
// ───────────────────────── DataProps 组合规则 ─────────────────────────
|
|
71
89
|
|
|
72
|
-
|
|
73
|
-
data: OverviewData;
|
|
74
|
-
/** chrome 文案 locale;省略时随宿主上下文(宿主外默认 "en")。 */
|
|
75
|
-
locale?: ReportLocale;
|
|
76
|
-
className?: string;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export interface GroupSummaryProps {
|
|
80
|
-
data: GroupSummaryData;
|
|
81
|
-
/** chrome 文案 locale;省略时随宿主上下文(宿主外默认 "en")。 */
|
|
82
|
-
locale?: ReportLocale;
|
|
83
|
-
className?: string;
|
|
84
|
-
}
|
|
90
|
+
type Never<T> = { [K in keyof T]?: never };
|
|
85
91
|
|
|
86
92
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* (docs/feature/reports/library.md「实体列表」)。
|
|
93
|
+
* 官方数据组件的统一 props 组合(docs/feature/reports/library/metric-views.md):
|
|
94
|
+
* data 形态(接收配套 *Data 的产物)或 spec 形态(Options 平铺 + 可选 input)。
|
|
90
95
|
*/
|
|
91
|
-
export
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
filter?: boolean;
|
|
95
|
-
/**
|
|
96
|
-
* 可选父路径:两面的行标签去掉与它相同的前缀,只显示 experiment id 末段,避免在已经以组为
|
|
97
|
-
* 标题的上下文里重复文件夹名(默认 `ExperimentComparison` 给每组传组键)。完整 id 仍是排序 /
|
|
98
|
-
* 着色 / 过滤 / 折叠的键;不传或它不是前缀(如根目录单例组)时显示完整 id。
|
|
99
|
-
*/
|
|
100
|
-
relativeTo?: string;
|
|
101
|
-
/** chrome 文案 locale;省略时随宿主上下文(宿主外默认 "en")。 */
|
|
102
|
-
locale?: ReportLocale;
|
|
103
|
-
className?: string;
|
|
104
|
-
}
|
|
96
|
+
export type DataProps<Data, Options, Presentation> =
|
|
97
|
+
| ({ data: Data; input?: never } & Never<Options> & Presentation)
|
|
98
|
+
| ({ data?: never; input?: ReportInput } & Options & Presentation);
|
|
105
99
|
|
|
106
|
-
|
|
107
|
-
items: EvalListItem[];
|
|
108
|
-
/** chrome 文案 locale;省略时随宿主上下文(宿主外默认 "en")。 */
|
|
109
|
-
locale?: ReportLocale;
|
|
110
|
-
className?: string;
|
|
111
|
-
}
|
|
100
|
+
// ───────────────────────── data 结构校验(版本漂移防线)─────────────────────────
|
|
112
101
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
/** items 被 slice 时如实显示剩余数量;省略即 items.length(未截断)。 */
|
|
116
|
-
total?: number;
|
|
117
|
-
/** chrome 文案 locale;省略时随宿主上下文(宿主外默认 "en")。 */
|
|
118
|
-
locale?: ReportLocale;
|
|
119
|
-
className?: string;
|
|
102
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
103
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
120
104
|
}
|
|
121
105
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
filter?: boolean;
|
|
132
|
-
/** chrome 文案 locale;省略时随宿主上下文(宿主外默认 "en")。 */
|
|
133
|
-
locale?: ReportLocale;
|
|
134
|
-
className?: string;
|
|
106
|
+
function isCell(value: unknown): boolean {
|
|
107
|
+
return (
|
|
108
|
+
isObject(value) &&
|
|
109
|
+
"value" in value &&
|
|
110
|
+
"display" in value &&
|
|
111
|
+
typeof value.samples === "number" &&
|
|
112
|
+
typeof value.total === "number" &&
|
|
113
|
+
Array.isArray(value.refs)
|
|
114
|
+
);
|
|
135
115
|
}
|
|
136
116
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
117
|
+
function isTally(value: unknown): boolean {
|
|
118
|
+
return (
|
|
119
|
+
isObject(value) &&
|
|
120
|
+
typeof value.passed === "number" &&
|
|
121
|
+
typeof value.failed === "number" &&
|
|
122
|
+
typeof value.errored === "number" &&
|
|
123
|
+
typeof value.skipped === "number"
|
|
124
|
+
);
|
|
143
125
|
}
|
|
144
126
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
127
|
+
type Validator = (data: unknown) => string | null;
|
|
128
|
+
|
|
129
|
+
function dataShapeError(component: string, dataFnName: string, shape: string, problem: string): Error {
|
|
130
|
+
return new Error(
|
|
131
|
+
`<${component}> received data that does not match the current ${shape} shape: ${problem}. ` +
|
|
132
|
+
`It may have been computed by a different niceeval version (component data carries no schemaVersion; the support window is same-version write and read). ` +
|
|
133
|
+
`Recompute it with ${dataFnName}() from this niceeval version, then re-render.`,
|
|
134
|
+
);
|
|
150
135
|
}
|
|
151
136
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
137
|
+
const validateTableData: Validator = (data) => {
|
|
138
|
+
if (!isObject(data)) return "expected an object";
|
|
139
|
+
if (typeof data.rowDimension !== "string") return 'missing "rowDimension" (string)';
|
|
140
|
+
if (!Array.isArray(data.columns)) return 'missing "columns" (array)';
|
|
141
|
+
if (!Array.isArray(data.rows)) return 'missing "rows" (array)';
|
|
142
|
+
for (const row of data.rows as unknown[]) {
|
|
143
|
+
if (!isObject(row) || typeof row.key !== "string" || !isObject(row.cells)) {
|
|
144
|
+
return 'each row needs { key, cells }';
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const validateMatrixData: Validator = (data) => {
|
|
151
|
+
if (!isObject(data)) return "expected an object";
|
|
152
|
+
if (typeof data.rowDimension !== "string" || typeof data.columnDimension !== "string") {
|
|
153
|
+
return 'missing "rowDimension" / "columnDimension" (string)';
|
|
154
|
+
}
|
|
155
|
+
if (!isObject(data.metric)) return 'missing "metric" (MetricColumn)';
|
|
156
|
+
if (!Array.isArray(data.cells)) return 'missing "cells" (array)';
|
|
157
|
+
return null;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const validateScatterData: Validator = (data) => {
|
|
161
|
+
if (!isObject(data)) return "expected an object";
|
|
162
|
+
if (typeof data.pointDimension !== "string") return 'missing "pointDimension" (string)';
|
|
163
|
+
if (!isObject(data.x) || !isObject(data.y)) return 'missing "x" / "y" (MetricColumn)';
|
|
164
|
+
if (!Array.isArray(data.rows)) return 'missing "rows" (array)';
|
|
165
|
+
for (const row of data.rows as unknown[]) {
|
|
166
|
+
if (!isObject(row) || typeof row.key !== "string" || !isCell(row.x) || !isCell(row.y)) {
|
|
167
|
+
return "each row needs { key, x: MetricCell, y: MetricCell }";
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const validateLineData: Validator = (data) => {
|
|
174
|
+
if (!isObject(data)) return "expected an object";
|
|
175
|
+
if (!isObject(data.x) || typeof (data.x as Record<string, unknown>).key !== "string") return 'missing "x" axis descriptor';
|
|
176
|
+
if (!isObject(data.y)) return 'missing "y" (MetricColumn)';
|
|
177
|
+
if (!Array.isArray(data.rows)) return 'missing "rows" (array)';
|
|
178
|
+
return null;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const validateScoreboardData: Validator = (data) => {
|
|
182
|
+
if (!isObject(data)) return "expected an object";
|
|
183
|
+
if (typeof data.rowDimension !== "string") return 'missing "rowDimension" (string)';
|
|
184
|
+
if (!Array.isArray(data.questions)) return 'missing "questions" (array)';
|
|
185
|
+
if (typeof data.fullMarks !== "number") return 'missing "fullMarks" (number)';
|
|
186
|
+
if (typeof data.ignoredEvals !== "number") return 'missing "ignoredEvals" (number)';
|
|
187
|
+
if (!Array.isArray(data.rows)) return 'missing "rows" (array)';
|
|
188
|
+
for (const row of data.rows as unknown[]) {
|
|
189
|
+
if (!isObject(row) || !isObject(row.total) || typeof (row.total as Record<string, unknown>).notRun !== "number") {
|
|
190
|
+
return 'each row needs { key, total: { value, display, notRun, unscorable, refs }, subjects }';
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const validateDeltaData: Validator = (data) => {
|
|
197
|
+
if (!isObject(data)) return "expected an object";
|
|
198
|
+
if (typeof data.byDimension !== "string") return 'missing "byDimension" (string)';
|
|
199
|
+
if (!Array.isArray(data.columns) || !Array.isArray(data.rows)) return 'missing "columns" / "rows" (array)';
|
|
200
|
+
for (const row of data.rows as unknown[]) {
|
|
201
|
+
if (!isObject(row) || row.label === undefined || !isObject(row.a) || !isObject(row.b)) {
|
|
202
|
+
return "each row needs { key, label, a, b, cells }";
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const validateScopeSummaryData: Validator = (data) => {
|
|
209
|
+
if (!isObject(data)) return "expected an object";
|
|
210
|
+
if (!isObject(data.range)) return 'missing "range" ({ earliestStartedAt, latestStartedAt })';
|
|
211
|
+
if (!isTally(data.evalVerdicts) || !isTally(data.attemptVerdicts)) {
|
|
212
|
+
return 'missing "evalVerdicts" / "attemptVerdicts" tallies';
|
|
213
|
+
}
|
|
214
|
+
if (!isCell(data.endToEndPassRate) || !isCell(data.totalCostUSD)) {
|
|
215
|
+
return 'missing "endToEndPassRate" / "totalCostUSD" (MetricCell)';
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const validateComparisonData: Validator = (data) => {
|
|
221
|
+
if (!isObject(data)) return "expected an object";
|
|
222
|
+
if (!Array.isArray(data.groups)) return 'missing "groups" (array)';
|
|
223
|
+
for (const group of data.groups as unknown[]) {
|
|
224
|
+
if (!isObject(group) || typeof group.key !== "string" || validateScopeSummaryData(group.summary) !== null) {
|
|
225
|
+
return "each group needs { key, summary, scatter, experiments }";
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const validateExperimentListData: Validator = (data) => {
|
|
232
|
+
if (!Array.isArray(data)) return "expected an array of ExperimentListItem";
|
|
233
|
+
for (const item of data as unknown[]) {
|
|
234
|
+
if (!isObject(item) || typeof item.experimentId !== "string" || !isTally(item.evalVerdicts) || !isCell(item.endToEndPassRate)) {
|
|
235
|
+
return "each item needs { experimentId, evalVerdicts, endToEndPassRate, costUSD, durationMs, tokens, evalRows, … }";
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const validateEvalListData: Validator = (data) => {
|
|
242
|
+
if (!Array.isArray(data)) return "expected an array of EvalListItem";
|
|
243
|
+
for (const item of data as unknown[]) {
|
|
244
|
+
if (!isObject(item) || typeof item.evalId !== "string" || !isCell(item.examScore) || !Array.isArray(item.attempts)) {
|
|
245
|
+
return "each item needs { experimentId, evalId, verdict, examScore, durationMs, costUSD, attempts }";
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const validateAttemptListData: Validator = (data) => {
|
|
252
|
+
if (!Array.isArray(data)) return "expected an array of AttemptListItem";
|
|
253
|
+
for (const item of data as unknown[]) {
|
|
254
|
+
if (!isObject(item) || typeof item.evalId !== "string" || !("failureSummary" in item) || !("costUSD" in item)) {
|
|
255
|
+
return "each item needs { experimentId, evalId, verdict, failureSummary, moreFailures, examScore, durationMs, costUSD, locator }";
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return null;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// ───────────────────────── spec / data 双形态的通用装配 ─────────────────────────
|
|
262
|
+
|
|
263
|
+
interface DataComponentDef<Data, Options, Presentation> {
|
|
264
|
+
name: string;
|
|
265
|
+
dataFnName: string;
|
|
266
|
+
shapeName: string;
|
|
267
|
+
dataFn: (input: ReportInput, options: Options) => Promise<Data>;
|
|
268
|
+
/** spec 形态的计算选项 prop 名(不含 input);未列出的 props 视为呈现选项原样保留。 */
|
|
269
|
+
specKeys: readonly string[];
|
|
270
|
+
validate: Validator;
|
|
271
|
+
web(props: { data: Data } & Presentation, ctx: WebContext): ReactNode;
|
|
272
|
+
text(props: { data: Data } & Presentation, ctx: TextContext): string;
|
|
159
273
|
}
|
|
160
274
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
275
|
+
function makeDataComponent<Data, Options, Presentation>(
|
|
276
|
+
def: DataComponentDef<Data, Options, Presentation>,
|
|
277
|
+
): ReportComponent<DataProps<Data, Options, Presentation>> {
|
|
278
|
+
type Props = Record<string, unknown>;
|
|
279
|
+
type Resolved = { data: Data } & Presentation;
|
|
280
|
+
|
|
281
|
+
const assertData = (data: unknown): Data => {
|
|
282
|
+
const problem = def.validate(data);
|
|
283
|
+
if (problem !== null) throw dataShapeError(def.name, def.dataFnName, def.shapeName, problem);
|
|
284
|
+
return data as Data;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const resolve = async (props: Props, ctx: ResolveContext): Promise<Resolved> => {
|
|
288
|
+
const givenSpec = def.specKeys.filter((key) => props[key] !== undefined);
|
|
289
|
+
if (props.data !== undefined) {
|
|
290
|
+
if (givenSpec.length > 0 || props.input !== undefined) {
|
|
291
|
+
const extras = [...givenSpec, ...(props.input !== undefined ? ["input"] : [])];
|
|
292
|
+
throw new Error(
|
|
293
|
+
`<${def.name}> got both \`data\` and spec field${extras.length > 1 ? "s" : ""} (${extras.join(", ")}) — the two data sources are exclusive and niceeval will not silently pick one. ` +
|
|
294
|
+
`Keep \`data\` (precomputed with ${def.dataFnName}()) and drop the spec fields, or drop \`data\` and let the pipeline compute from the spec.`,
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
assertData(props.data);
|
|
298
|
+
return props as unknown as Resolved;
|
|
299
|
+
}
|
|
300
|
+
const options: Record<string, unknown> = {};
|
|
301
|
+
for (const key of givenSpec) options[key] = props[key];
|
|
302
|
+
const input = (props.input as ReportInput | undefined) ?? ctx.input;
|
|
303
|
+
const data = await memoFetchOf(ctx)(def.dataFn, input, options, () =>
|
|
304
|
+
def.dataFn(input, options as Options),
|
|
305
|
+
);
|
|
306
|
+
const rest: Record<string, unknown> = { ...props };
|
|
307
|
+
delete rest.input;
|
|
308
|
+
for (const key of def.specKeys) delete rest[key];
|
|
309
|
+
return { ...rest, data } as unknown as Resolved;
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const component = defineComponent<Props, Resolved>({
|
|
313
|
+
resolve,
|
|
314
|
+
web: (props, ctx) => {
|
|
315
|
+
assertData((props as { data?: unknown }).data);
|
|
316
|
+
return def.web(props, ctx);
|
|
317
|
+
},
|
|
318
|
+
text: (props, ctx) => {
|
|
319
|
+
assertData((props as { data?: unknown }).data);
|
|
320
|
+
return def.text(props, ctx);
|
|
321
|
+
},
|
|
322
|
+
}) as unknown as ReportComponent<DataProps<Data, Options, Presentation>>;
|
|
323
|
+
component.displayName = def.name;
|
|
324
|
+
return component;
|
|
164
325
|
}
|
|
165
326
|
|
|
166
|
-
/**
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
export type MetricScatterProps =
|
|
173
|
-
| (MetricScatterResolvedProps & {
|
|
174
|
-
selection?: never;
|
|
175
|
-
points?: never;
|
|
176
|
-
series?: never;
|
|
177
|
-
x?: never;
|
|
178
|
-
y?: never;
|
|
179
|
-
})
|
|
180
|
-
| ({ data?: never } & ScatterDataOptions & MetricScatterRenderProps & { selection: Selection });
|
|
181
|
-
|
|
182
|
-
export interface MetricLineProps {
|
|
183
|
-
data: LineData;
|
|
184
|
-
pointHref?: (row: LineData["rows"][number]) => string;
|
|
185
|
-
/** chrome 文案 locale;省略时随宿主上下文(宿主外默认 "en")。 */
|
|
186
|
-
locale?: ReportLocale;
|
|
187
|
-
className?: string;
|
|
327
|
+
/** 宿主内缺省接证据室,显式 prop 覆盖;宿主外不传 attemptHref 就是纯展示。 */
|
|
328
|
+
function hrefOf(
|
|
329
|
+
props: { attemptHref?: (locator: AttemptLocator) => string },
|
|
330
|
+
ctx: WebContext,
|
|
331
|
+
): ((locator: AttemptLocator) => string) | undefined {
|
|
332
|
+
return props.attemptHref ?? (isHostWebContextActive() ? ctx.attemptHref : undefined);
|
|
188
333
|
}
|
|
189
334
|
|
|
190
|
-
|
|
191
|
-
|
|
335
|
+
// ───────────────────────── 呈现选项类型 ─────────────────────────
|
|
336
|
+
|
|
337
|
+
interface ChromeProps {
|
|
192
338
|
/** chrome 文案 locale;省略时随宿主上下文(宿主外默认 "en")。 */
|
|
193
339
|
locale?: ReportLocale;
|
|
194
340
|
className?: string;
|
|
195
341
|
}
|
|
196
342
|
|
|
197
|
-
// ─────────────────────────
|
|
343
|
+
// ───────────────────────── 概览组件 ─────────────────────────
|
|
198
344
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
345
|
+
export type ScopeSummaryProps = DataProps<
|
|
346
|
+
ScopeSummaryData,
|
|
347
|
+
Record<never, never>,
|
|
348
|
+
ChromeProps & {
|
|
349
|
+
/** 显示哪一级计票;默认 "eval"。data 恒携带两级,votes 只选择呈现。 */
|
|
350
|
+
votes?: "eval" | "attempt";
|
|
351
|
+
}
|
|
352
|
+
>;
|
|
203
353
|
|
|
204
|
-
/**
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
354
|
+
/** 范围摘要卡:时间窗、数量、两级计票、端到端成功率与总成本。 */
|
|
355
|
+
export const ScopeSummary = makeDataComponent<
|
|
356
|
+
ScopeSummaryData,
|
|
357
|
+
Record<never, never>,
|
|
358
|
+
ChromeProps & { votes?: "eval" | "attempt" }
|
|
359
|
+
>({
|
|
360
|
+
name: "ScopeSummary",
|
|
361
|
+
dataFnName: "scopeSummaryData",
|
|
362
|
+
shapeName: "ScopeSummaryData",
|
|
363
|
+
dataFn: (input) => scopeSummaryData(input),
|
|
364
|
+
specKeys: [],
|
|
365
|
+
validate: validateScopeSummaryData,
|
|
366
|
+
web: (props, ctx) => <ScopeSummaryWeb {...props} locale={props.locale ?? ctx.locale} />,
|
|
367
|
+
text: (props, ctx) => scopeSummaryText(props.data, props.votes ?? "eval", ctx),
|
|
368
|
+
}) as unknown as ReportComponent<ScopeSummaryProps>;
|
|
209
369
|
|
|
210
|
-
|
|
211
|
-
export const RunOverview: ReportComponent<RunOverviewProps> & { data: typeof overviewData } = Object.assign(
|
|
212
|
-
defineComponent<RunOverviewProps>({
|
|
213
|
-
web: (props, ctx) => <RunOverviewWeb {...props} locale={props.locale ?? ctx.locale} />,
|
|
214
|
-
text: ({ data }, ctx) => overviewText(data, ctx),
|
|
215
|
-
}),
|
|
216
|
-
{ data: overviewData },
|
|
217
|
-
);
|
|
218
|
-
RunOverview.displayName = "RunOverview";
|
|
370
|
+
export type ExperimentComparisonProps = DataProps<ExperimentComparisonData, Record<never, never>, ChromeProps>;
|
|
219
371
|
|
|
220
372
|
/**
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
373
|
+
* 内建报告的默认组合件:先把 input 按可比组分区,再为每组分别计算 ScopeSummary、
|
|
374
|
+
* 成本 × 端到端成功率散点和 ExperimentList。web 面持有完整组索引并一次聚焦一组;
|
|
375
|
+
* text 面命中多个组时只显示组索引与可执行的单组查看命令,命中单组时才输出完整散点与列表。
|
|
224
376
|
*/
|
|
225
|
-
export const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
377
|
+
export const ExperimentComparison = makeDataComponent<ExperimentComparisonData, Record<never, never>, ChromeProps>({
|
|
378
|
+
name: "ExperimentComparison",
|
|
379
|
+
dataFnName: "experimentComparisonData",
|
|
380
|
+
shapeName: "ExperimentComparisonData",
|
|
381
|
+
dataFn: (input) => experimentComparisonData(input),
|
|
382
|
+
specKeys: [],
|
|
383
|
+
validate: validateComparisonData,
|
|
384
|
+
web: (props, ctx) => (
|
|
385
|
+
<ExperimentComparisonView
|
|
386
|
+
data={props.data}
|
|
387
|
+
locale={props.locale ?? ctx.locale}
|
|
388
|
+
className={props.className}
|
|
389
|
+
attemptHref={isHostWebContextActive() ? ctx.attemptHref : undefined}
|
|
390
|
+
/>
|
|
391
|
+
),
|
|
392
|
+
text: (props, ctx) => experimentComparisonText(props.data, props.className, ctx),
|
|
393
|
+
}) as unknown as ReportComponent<ExperimentComparisonProps>;
|
|
394
|
+
|
|
395
|
+
// ───────────────────────── 实体列表 ─────────────────────────
|
|
396
|
+
|
|
397
|
+
interface EntityListChrome extends ChromeProps {
|
|
398
|
+
attemptHref?: (locator: AttemptLocator) => string;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export type ExperimentListProps = DataProps<
|
|
402
|
+
readonly ExperimentListItem[],
|
|
403
|
+
EntityListDataOptions,
|
|
404
|
+
EntityListChrome & {
|
|
405
|
+
/** web 面在比较表前显示实验过滤框;text 面忽略。 */
|
|
406
|
+
filter?: boolean;
|
|
407
|
+
/**
|
|
408
|
+
* 可选父路径:两面的行标签去掉与它相同的前缀,只显示 experiment id 末段(默认
|
|
409
|
+
* `ExperimentComparison` 给每组传组键)。完整 id 仍是排序 / 着色 / 过滤 / 折叠的键。
|
|
410
|
+
*/
|
|
411
|
+
relativeTo?: string;
|
|
412
|
+
}
|
|
413
|
+
>;
|
|
414
|
+
|
|
415
|
+
/** 实验列表:每项一个 experiment,固定八列比较表 + 展开到 Eval / Attempt。 */
|
|
416
|
+
export const ExperimentList = makeDataComponent<
|
|
417
|
+
readonly ExperimentListItem[],
|
|
418
|
+
EntityListDataOptions,
|
|
419
|
+
EntityListChrome & { filter?: boolean; relativeTo?: string }
|
|
420
|
+
>({
|
|
421
|
+
name: "ExperimentList",
|
|
422
|
+
dataFnName: "experimentListData",
|
|
423
|
+
shapeName: "ExperimentListItem[]",
|
|
424
|
+
dataFn: (input, options) => experimentListData(input, options),
|
|
425
|
+
specKeys: ["redact"],
|
|
426
|
+
validate: validateExperimentListData,
|
|
427
|
+
web: (props, ctx) => (
|
|
428
|
+
<ExperimentListWeb
|
|
429
|
+
data={props.data}
|
|
430
|
+
filter={props.filter}
|
|
431
|
+
relativeTo={props.relativeTo}
|
|
432
|
+
locale={props.locale ?? ctx.locale}
|
|
433
|
+
attemptHref={hrefOf(props, ctx) ?? ctx.attemptHref}
|
|
434
|
+
className={props.className}
|
|
435
|
+
/>
|
|
436
|
+
),
|
|
437
|
+
text: (props, ctx) => experimentListText(props.data, ctx, props.relativeTo),
|
|
438
|
+
}) as unknown as ReportComponent<ExperimentListProps>;
|
|
439
|
+
|
|
440
|
+
export type EvalListProps = DataProps<readonly EvalListItem[], EntityListDataOptions, EntityListChrome>;
|
|
441
|
+
|
|
442
|
+
/** Eval 列表:每项一个 experimentId + evalId,展开到这道题的 Attempt。 */
|
|
443
|
+
export const EvalList = makeDataComponent<readonly EvalListItem[], EntityListDataOptions, EntityListChrome>({
|
|
444
|
+
name: "EvalList",
|
|
445
|
+
dataFnName: "evalListData",
|
|
446
|
+
shapeName: "EvalListItem[]",
|
|
447
|
+
dataFn: (input, options) => evalListData(input, options),
|
|
448
|
+
specKeys: ["redact"],
|
|
449
|
+
validate: validateEvalListData,
|
|
450
|
+
web: (props, ctx) => (
|
|
451
|
+
<EvalListWeb
|
|
452
|
+
data={props.data}
|
|
453
|
+
locale={props.locale ?? ctx.locale}
|
|
454
|
+
attemptHref={hrefOf(props, ctx) ?? ctx.attemptHref}
|
|
455
|
+
className={props.className}
|
|
456
|
+
/>
|
|
457
|
+
),
|
|
458
|
+
text: (props, ctx) => evalListText(props.data, ctx),
|
|
459
|
+
}) as unknown as ReportComponent<EvalListProps>;
|
|
460
|
+
|
|
461
|
+
export type AttemptListProps = DataProps<
|
|
462
|
+
readonly AttemptListItem[],
|
|
463
|
+
EntityListDataOptions,
|
|
464
|
+
EntityListChrome & {
|
|
465
|
+
/** 过滤 / 截断前的总数;省略时等于 data 长度。 */
|
|
466
|
+
total?: number;
|
|
467
|
+
}
|
|
468
|
+
>;
|
|
469
|
+
|
|
470
|
+
/** Attempt 列表:实体列表的叶子层,每项一次 attempt 的判定、单行摘要与 locator。 */
|
|
471
|
+
export const AttemptList = makeDataComponent<
|
|
472
|
+
readonly AttemptListItem[],
|
|
473
|
+
EntityListDataOptions,
|
|
474
|
+
EntityListChrome & { total?: number }
|
|
475
|
+
>({
|
|
476
|
+
name: "AttemptList",
|
|
477
|
+
dataFnName: "attemptListData",
|
|
478
|
+
shapeName: "AttemptListItem[]",
|
|
479
|
+
dataFn: (input, options) => attemptListData(input, options),
|
|
480
|
+
specKeys: ["redact"],
|
|
481
|
+
validate: validateAttemptListData,
|
|
482
|
+
web: (props, ctx) => (
|
|
483
|
+
<AttemptListWeb
|
|
484
|
+
data={props.data}
|
|
485
|
+
total={props.total}
|
|
486
|
+
locale={props.locale ?? ctx.locale}
|
|
487
|
+
attemptHref={hrefOf(props, ctx) ?? ctx.attemptHref}
|
|
488
|
+
className={props.className}
|
|
489
|
+
/>
|
|
490
|
+
),
|
|
491
|
+
text: (props, ctx) => attemptListText(props.data, props.total, ctx),
|
|
492
|
+
}) as unknown as ReportComponent<AttemptListProps>;
|
|
493
|
+
|
|
494
|
+
// ───────────────────────── FailureList(官方组合件)─────────────────────────
|
|
495
|
+
|
|
496
|
+
export interface FailureListProps {
|
|
497
|
+
/** 显示的最大条数;默认 20。 */
|
|
498
|
+
limit?: number;
|
|
499
|
+
/** 默认宿主注入的 Scope。 */
|
|
500
|
+
input?: ReportInput;
|
|
501
|
+
redact?: (text: string) => string;
|
|
502
|
+
attemptHref?: (locator: AttemptLocator) => string;
|
|
503
|
+
locale?: ReportLocale;
|
|
504
|
+
className?: string;
|
|
505
|
+
}
|
|
233
506
|
|
|
234
507
|
/**
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
508
|
+
* 「现在有哪些失败要处理」的成品组合件:内部就是 attemptListData → 过滤 → AttemptList
|
|
509
|
+
* data 形态,与手写组合严格等价、没有私有能力(docs/feature/reports/library/entity-lists.md)。
|
|
510
|
+
* verdict ∈ failed / errored,按 attempt 开始时间降序(同刻按 locator 字典序),
|
|
511
|
+
* 截断到 limit(默认 20),total 报告截断前总数。
|
|
239
512
|
*/
|
|
240
|
-
export const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
513
|
+
export const FailureList = defineComponent<FailureListProps>(async (props, ctx) => {
|
|
514
|
+
const input = props.input ?? ctx.scope;
|
|
515
|
+
const all = await attemptListData(input, props.redact !== undefined ? { redact: props.redact } : undefined);
|
|
516
|
+
// attempt 开始时间不在列表条目里(它不是列表展示字段);从同一 input 的读取面按 locator 对回。
|
|
517
|
+
const startedAtByLocator = new Map<string, string>();
|
|
518
|
+
for (const item of collectItems(resolveInput(input).snapshots)) {
|
|
519
|
+
startedAtByLocator.set(locatorOf(item), item.attempt.result.startedAt ?? "");
|
|
520
|
+
}
|
|
521
|
+
const failures = all
|
|
522
|
+
.filter((item) => item.verdict === "failed" || item.verdict === "errored")
|
|
523
|
+
.sort((a, b) => {
|
|
524
|
+
const ta = startedAtByLocator.get(a.locator) ?? "";
|
|
525
|
+
const tb = startedAtByLocator.get(b.locator) ?? "";
|
|
526
|
+
if (ta !== tb) return ta < tb ? 1 : -1; // 最近的失败在前
|
|
527
|
+
return a.locator < b.locator ? -1 : a.locator > b.locator ? 1 : 0;
|
|
528
|
+
});
|
|
529
|
+
const limit = props.limit ?? 20;
|
|
530
|
+
return (
|
|
531
|
+
<AttemptList
|
|
532
|
+
data={failures.slice(0, limit)}
|
|
533
|
+
total={failures.length}
|
|
534
|
+
attemptHref={props.attemptHref}
|
|
535
|
+
locale={props.locale}
|
|
536
|
+
className={props.className}
|
|
537
|
+
/>
|
|
247
538
|
);
|
|
248
|
-
|
|
539
|
+
});
|
|
540
|
+
FailureList.displayName = "FailureList";
|
|
249
541
|
|
|
250
|
-
|
|
251
|
-
* Eval 列表:每项一个 `experimentId + evalId`,固定展示判定、Attempt 数、分数、成本、耗时
|
|
252
|
-
* 与失败原因;展开到这道题的 Attempt(与 AttemptList 同一份 AttemptListItem)。
|
|
253
|
-
*/
|
|
254
|
-
export const EvalList: ReportComponent<EvalListProps> & { data: typeof evalListData } = Object.assign(
|
|
255
|
-
defineComponent<EvalListProps>({
|
|
256
|
-
web: (props, ctx) => <EvalListWeb {...props} locale={props.locale ?? ctx.locale} attemptHref={ctx.attemptHref} />,
|
|
257
|
-
text: ({ items }, ctx) => evalListText(items, ctx),
|
|
258
|
-
}),
|
|
259
|
-
{ data: evalListData },
|
|
260
|
-
);
|
|
261
|
-
EvalList.displayName = "EvalList";
|
|
542
|
+
// ───────────────────────── 指标组件 ─────────────────────────
|
|
262
543
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
*/
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}),
|
|
273
|
-
{ data: attemptListData },
|
|
274
|
-
);
|
|
275
|
-
AttemptList.displayName = "AttemptList";
|
|
544
|
+
export type MetricTableProps = DataProps<
|
|
545
|
+
TableData,
|
|
546
|
+
MetricTableOptions,
|
|
547
|
+
ChromeProps & {
|
|
548
|
+
/** web 面在表格前渲染过滤输入框(enhance.js 接管);无 JS 时表格内容依旧完整。 */
|
|
549
|
+
filter?: boolean;
|
|
550
|
+
attemptHref?: (locator: AttemptLocator) => string;
|
|
551
|
+
}
|
|
552
|
+
>;
|
|
276
553
|
|
|
277
554
|
/** 榜单:一行一个维度值、一列一个指标,回答「谁整体更好」。 */
|
|
278
|
-
export const MetricTable
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
555
|
+
export const MetricTable = makeDataComponent<
|
|
556
|
+
TableData,
|
|
557
|
+
MetricTableOptions,
|
|
558
|
+
ChromeProps & { filter?: boolean; attemptHref?: (locator: AttemptLocator) => string }
|
|
559
|
+
>({
|
|
560
|
+
name: "MetricTable",
|
|
561
|
+
dataFnName: "metricTableData",
|
|
562
|
+
shapeName: "TableData",
|
|
563
|
+
dataFn: metricTableData,
|
|
564
|
+
specKeys: ["rows", "columns", "sort", "evals"],
|
|
565
|
+
validate: validateTableData,
|
|
566
|
+
web: (props, ctx) => (
|
|
567
|
+
<MetricTableWeb
|
|
568
|
+
data={props.data}
|
|
569
|
+
filter={props.filter}
|
|
570
|
+
locale={props.locale ?? ctx.locale}
|
|
571
|
+
attemptHref={hrefOf(props, ctx)}
|
|
572
|
+
className={props.className}
|
|
573
|
+
/>
|
|
574
|
+
),
|
|
575
|
+
text: (props, ctx) => tableText(props.data, ctx),
|
|
576
|
+
}) as unknown as ReportComponent<MetricTableProps>;
|
|
577
|
+
|
|
578
|
+
export type MetricMatrixProps = DataProps<
|
|
579
|
+
MatrixData,
|
|
580
|
+
MetricMatrixOptions,
|
|
581
|
+
ChromeProps & { attemptHref?: (locator: AttemptLocator) => string }
|
|
582
|
+
>;
|
|
583
|
+
export type MetricBarsProps = MetricMatrixProps;
|
|
292
584
|
|
|
293
585
|
/** 逐题格子:行 × 列两个维度、格子里一个指标,回答「哪道题谁挂了」。 */
|
|
294
|
-
export const MetricMatrix
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
export const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
586
|
+
export const MetricMatrix = makeDataComponent<
|
|
587
|
+
MatrixData,
|
|
588
|
+
MetricMatrixOptions,
|
|
589
|
+
ChromeProps & { attemptHref?: (locator: AttemptLocator) => string }
|
|
590
|
+
>({
|
|
591
|
+
name: "MetricMatrix",
|
|
592
|
+
dataFnName: "metricMatrixData",
|
|
593
|
+
shapeName: "MatrixData",
|
|
594
|
+
dataFn: metricMatrixData,
|
|
595
|
+
specKeys: ["rows", "columns", "cell", "evals"],
|
|
596
|
+
validate: validateMatrixData,
|
|
597
|
+
web: (props, ctx) => (
|
|
598
|
+
<MetricMatrixWeb data={props.data} locale={props.locale ?? ctx.locale} attemptHref={hrefOf(props, ctx)} className={props.className} />
|
|
599
|
+
),
|
|
600
|
+
text: (props, ctx) => matrixText(props.data, ctx),
|
|
601
|
+
}) as unknown as ReportComponent<MetricMatrixProps>;
|
|
602
|
+
|
|
603
|
+
/** 分组条形:同一份矩阵数据的另一种摆法;与 MetricMatrix 写同一份 spec 时只计算一次。 */
|
|
604
|
+
export const MetricBars = makeDataComponent<
|
|
605
|
+
MatrixData,
|
|
606
|
+
MetricMatrixOptions,
|
|
607
|
+
ChromeProps & { attemptHref?: (locator: AttemptLocator) => string }
|
|
608
|
+
>({
|
|
609
|
+
name: "MetricBars",
|
|
610
|
+
dataFnName: "metricMatrixData",
|
|
611
|
+
shapeName: "MatrixData",
|
|
612
|
+
dataFn: metricMatrixData,
|
|
613
|
+
specKeys: ["rows", "columns", "cell", "evals"],
|
|
614
|
+
validate: validateMatrixData,
|
|
615
|
+
web: (props, ctx) => (
|
|
616
|
+
<MetricBarsWeb data={props.data} locale={props.locale ?? ctx.locale} attemptHref={hrefOf(props, ctx)} className={props.className} />
|
|
617
|
+
),
|
|
618
|
+
text: (props, ctx) => barsText(props.data, ctx),
|
|
619
|
+
}) as unknown as ReportComponent<MetricBarsProps>;
|
|
620
|
+
|
|
621
|
+
export type ScoreboardProps = DataProps<
|
|
622
|
+
ScoreboardData,
|
|
623
|
+
ScoreboardOptions,
|
|
624
|
+
ChromeProps & { attemptHref?: (locator: AttemptLocator) => string }
|
|
625
|
+
>;
|
|
626
|
+
|
|
627
|
+
/** 考试成绩单:总分 + 分科小计,固定分母、notRun / unscorable 分开如实报。 */
|
|
628
|
+
export const Scoreboard = makeDataComponent<
|
|
629
|
+
ScoreboardData,
|
|
630
|
+
ScoreboardOptions,
|
|
631
|
+
ChromeProps & { attemptHref?: (locator: AttemptLocator) => string }
|
|
632
|
+
>({
|
|
633
|
+
name: "Scoreboard",
|
|
634
|
+
dataFnName: "scoreboardData",
|
|
635
|
+
shapeName: "ScoreboardData",
|
|
636
|
+
dataFn: scoreboardData,
|
|
637
|
+
specKeys: ["rows", "questions", "subject", "weights", "fullMarks", "score"],
|
|
638
|
+
validate: validateScoreboardData,
|
|
639
|
+
web: (props, ctx) => <ScoreboardWeb data={props.data} locale={props.locale ?? ctx.locale} className={props.className} />,
|
|
640
|
+
text: (props, ctx) => scoreboardText(props.data, ctx),
|
|
641
|
+
}) as unknown as ReportComponent<ScoreboardProps>;
|
|
642
|
+
|
|
643
|
+
export type MetricScatterProps = DataProps<
|
|
644
|
+
ScatterData,
|
|
645
|
+
MetricScatterOptions,
|
|
646
|
+
ChromeProps & { pointHref?: (row: ScatterData["rows"][number]) => string }
|
|
647
|
+
>;
|
|
648
|
+
|
|
649
|
+
/** 两个指标之间的取舍:每个点一个维度值,x / y 各一个指标,series 只决定颜色和分组。 */
|
|
650
|
+
export const MetricScatter = makeDataComponent<
|
|
651
|
+
ScatterData,
|
|
652
|
+
MetricScatterOptions,
|
|
653
|
+
ChromeProps & { pointHref?: (row: ScatterData["rows"][number]) => string }
|
|
654
|
+
>({
|
|
655
|
+
name: "MetricScatter",
|
|
656
|
+
dataFnName: "metricScatterData",
|
|
657
|
+
shapeName: "ScatterData",
|
|
658
|
+
dataFn: metricScatterData,
|
|
659
|
+
specKeys: ["points", "series", "x", "y", "evals"],
|
|
660
|
+
validate: validateScatterData,
|
|
661
|
+
web: (props, ctx) => (
|
|
662
|
+
<MetricScatterWeb data={props.data} pointHref={props.pointHref} locale={props.locale ?? ctx.locale} className={props.className} />
|
|
663
|
+
),
|
|
664
|
+
text: (props, ctx) => scatterText(props.data, ctx),
|
|
665
|
+
}) as unknown as ReportComponent<MetricScatterProps>;
|
|
666
|
+
|
|
667
|
+
export type MetricLineProps = DataProps<
|
|
668
|
+
LineData,
|
|
669
|
+
MetricLineOptions,
|
|
670
|
+
ChromeProps & { pointHref?: (row: LineData["rows"][number]) => string }
|
|
671
|
+
>;
|
|
672
|
+
|
|
673
|
+
/** 趋势线:x 是 NumericAxis(参数轴),点身份 = (series, x)。 */
|
|
674
|
+
export const MetricLine = makeDataComponent<
|
|
675
|
+
LineData,
|
|
676
|
+
MetricLineOptions,
|
|
677
|
+
ChromeProps & { pointHref?: (row: LineData["rows"][number]) => string }
|
|
678
|
+
>({
|
|
679
|
+
name: "MetricLine",
|
|
680
|
+
dataFnName: "metricLineData",
|
|
681
|
+
shapeName: "LineData",
|
|
682
|
+
dataFn: metricLineData,
|
|
683
|
+
specKeys: ["x", "series", "y", "evals"],
|
|
684
|
+
validate: validateLineData,
|
|
685
|
+
web: (props, ctx) => (
|
|
686
|
+
<MetricLineWeb data={props.data} pointHref={props.pointHref} locale={props.locale ?? ctx.locale} className={props.className} />
|
|
687
|
+
),
|
|
688
|
+
text: (props, ctx) => lineText(props.data, ctx),
|
|
689
|
+
}) as unknown as ReportComponent<MetricLineProps>;
|
|
690
|
+
|
|
691
|
+
export type DeltaTableProps = DataProps<
|
|
692
|
+
DeltaData,
|
|
693
|
+
DeltaTableOptions,
|
|
694
|
+
ChromeProps & { attemptHref?: (locator: AttemptLocator) => string }
|
|
695
|
+
>;
|
|
696
|
+
|
|
697
|
+
/** 成对对比:每行一对(字面声明或 pairsByFlag 派生),格子里 A、B、Δ 三个值。 */
|
|
698
|
+
export const DeltaTable = makeDataComponent<
|
|
699
|
+
DeltaData,
|
|
700
|
+
DeltaTableOptions,
|
|
701
|
+
ChromeProps & { attemptHref?: (locator: AttemptLocator) => string }
|
|
702
|
+
>({
|
|
703
|
+
name: "DeltaTable",
|
|
704
|
+
dataFnName: "deltaTableData",
|
|
705
|
+
shapeName: "DeltaData",
|
|
706
|
+
dataFn: deltaTableData,
|
|
707
|
+
specKeys: ["by", "pairs", "metrics", "evals"],
|
|
708
|
+
validate: validateDeltaData,
|
|
709
|
+
web: (props, ctx) => <DeltaTableWeb data={props.data} locale={props.locale ?? ctx.locale} className={props.className} />,
|
|
710
|
+
text: (props, ctx) => deltaText(props.data, ctx),
|
|
711
|
+
}) as unknown as ReportComponent<DeltaTableProps>;
|