niceeval 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INDEX.md +77 -45
- package/dist/agents/types.d.ts +28 -6
- package/dist/i18n/en.d.ts +2 -0
- package/dist/i18n/zh-CN.d.ts +2 -0
- package/dist/report/built-in/index.d.ts +3 -2
- package/dist/report/built-in/index.js +7 -8
- package/dist/report/built-in/standard.d.ts +1 -0
- package/dist/report/built-in/standard.js +30 -0
- package/dist/report/components.d.ts +69 -2
- package/dist/report/components.js +152 -3
- package/dist/report/compute.d.ts +28 -1
- package/dist/report/compute.js +123 -0
- package/dist/report/index.d.ts +4 -4
- package/dist/report/index.js +3 -2
- package/dist/report/locale.d.ts +39 -1
- package/dist/report/locale.js +69 -0
- package/dist/report/react/AttemptList.d.ts +3 -1
- package/dist/report/react/AttemptList.js +3 -3
- package/dist/report/react/CopyFixPrompt.d.ts +12 -0
- package/dist/report/react/CopyFixPrompt.js +12 -0
- package/dist/report/react/HeroCard.d.ts +13 -0
- package/dist/report/react/HeroCard.js +35 -0
- package/dist/report/react/PoweredBy.d.ts +5 -0
- package/dist/report/react/PoweredBy.js +7 -0
- package/dist/report/react/ScopeWarnings.d.ts +12 -0
- package/dist/report/react/ScopeWarnings.js +18 -0
- package/dist/report/react/TraceWaterfall.d.ts +14 -0
- package/dist/report/react/TraceWaterfall.js +22 -0
- package/dist/report/react/index.d.ts +6 -1
- package/dist/report/react/index.js +6 -0
- package/dist/report/report.d.ts +22 -6
- package/dist/report/report.js +66 -53
- package/dist/report/scope-warnings.d.ts +28 -0
- package/dist/report/scope-warnings.js +101 -0
- package/dist/report/text/faces.d.ts +19 -1
- package/dist/report/text/faces.js +61 -0
- package/dist/report/tree.js +6 -1
- package/dist/report/types.d.ts +45 -0
- package/dist/report/web.d.ts +5 -4
- package/dist/report/web.js +7 -22
- package/dist/results/select.d.ts +15 -2
- package/dist/results/select.js +75 -10
- package/dist/results/types.d.ts +26 -0
- package/dist/runner/fingerprint.d.ts +3 -3
- package/dist/runner/sandbox-selection.d.ts +12 -0
- package/dist/runner/types.d.ts +18 -6
- package/dist/sandbox/types.d.ts +12 -0
- package/docs-site/zh/explanation/evals.mdx +2 -1
- package/docs-site/zh/explanation/experiment.mdx +2 -0
- package/docs-site/zh/how-to/custom-reports.mdx +22 -6
- package/docs-site/zh/how-to/publish-report.mdx +5 -4
- package/docs-site/zh/how-to/viewing-results.mdx +3 -3
- package/docs-site/zh/how-to/write-experiment.mdx +40 -1
- package/docs-site/zh/reference/builtin-agents.mdx +40 -3
- package/docs-site/zh/reference/cli.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +8 -0
- package/docs-site/zh/reference/official-adapters.mdx +10 -5
- package/package.json +2 -1
- package/src/agents/bub.ts +13 -1
- package/src/agents/claude-code.test.ts +43 -1
- package/src/agents/claude-code.ts +32 -14
- package/src/agents/codex.test.ts +168 -1
- package/src/agents/codex.ts +51 -15
- package/src/agents/mcp.ts +31 -0
- package/src/agents/post-setup.ts +33 -0
- package/src/agents/types.ts +28 -7
- package/src/cli.ts +8 -6
- package/src/define.ts +3 -0
- package/src/i18n/en.ts +5 -2
- package/src/i18n/zh-CN.ts +5 -1
- package/src/index.ts +1 -0
- package/src/report/built-in/index.tsx +8 -7
- package/src/report/built-in/standard.tsx +59 -0
- package/src/report/components.tsx +218 -2
- package/src/report/compute.ts +138 -1
- package/src/report/dual-render.test.tsx +139 -12
- package/src/report/index.ts +20 -0
- package/src/report/locale.ts +83 -1
- package/src/report/react/AttemptList.tsx +13 -1
- package/src/report/react/CopyFixPrompt.tsx +37 -0
- package/src/report/react/HeroCard.tsx +59 -0
- package/src/report/react/PoweredBy.tsx +20 -0
- package/src/report/react/ScopeWarnings.tsx +74 -0
- package/src/report/react/TraceWaterfall.tsx +78 -0
- package/src/report/react/enhance.js +14 -0
- package/src/report/react/index.tsx +11 -0
- package/src/report/react/styles.css +187 -7
- package/src/report/report.ts +97 -62
- package/src/report/scope-warnings.ts +155 -0
- package/src/report/site-components.test.tsx +526 -0
- package/src/report/text/faces.ts +66 -0
- package/src/report/tree.ts +8 -1
- package/src/report/types.ts +51 -0
- package/src/report/web.ts +7 -40
- package/src/results/host-equivalence.test.ts +5 -1
- package/src/results/open.ts +5 -4
- package/src/results/results.test.ts +78 -1
- package/src/results/select.ts +78 -10
- package/src/results/types.ts +27 -0
- package/src/runner/attempt.ts +10 -9
- package/src/runner/discover.test.ts +9 -1
- package/src/runner/discover.ts +3 -3
- package/src/runner/fingerprint.ts +9 -4
- package/src/runner/ledger.test.ts +30 -1
- package/src/runner/ledger.ts +26 -4
- package/src/runner/run.ts +5 -1
- package/src/runner/sandbox-selection.test.ts +131 -0
- package/src/runner/sandbox-selection.ts +110 -0
- package/src/runner/types.ts +19 -2
- package/src/sandbox/types.ts +6 -0
- package/src/show/index.ts +17 -10
- package/src/show/render.ts +11 -11
- package/src/show/report-host.test.ts +32 -15
- package/src/show/report-host.ts +5 -4
- package/src/show/show.test.ts +140 -3
- package/src/view/app/App.test.tsx +78 -17
- package/src/view/app/App.tsx +17 -78
- package/src/view/app/components/CopyControls.tsx +4 -42
- package/src/view/app/i18n.ts +5 -227
- package/src/view/app/lib/rows.ts +3 -21
- package/src/view/app/shared.ts +1 -3
- package/src/view/app/types.ts +2 -2
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +20 -20
- package/src/view/data.ts +2 -2
- package/src/view/shared/types.ts +11 -6
- package/src/view/styles.css +6 -266
- package/src/view/view-report.test.ts +59 -22
- package/src/view/app/components/LazyArtifact.tsx +0 -51
- package/src/view/app/components/SkippedRunsBanner.tsx +0 -140
- package/src/view/app/pages/AttemptsPage.tsx +0 -80
- package/src/view/app/pages/TracesPage.tsx +0 -35
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// factory 的 postSetup 钩子执行器 —— Claude Code / Codex / Bub 共用。
|
|
2
|
+
// 契约见 docs/feature/adapters/library/coding-agent-extensions.md「安装后运行脚本」:
|
|
3
|
+
// 在 adapter 全部安装步骤(含 manifest)之后按数组顺序执行;复用 SandboxHook 的窄上下文;
|
|
4
|
+
// 返回的 cleanup 合成一个 LIFO 闭包,由 runner 与 agent teardown 一起收尾;钩子抛错
|
|
5
|
+
// 直接传播(attempt errored)。
|
|
6
|
+
|
|
7
|
+
import type { Cleanup } from "../shared/types.ts";
|
|
8
|
+
import type { Sandbox, SandboxHook, SandboxHookContext } from "../sandbox/types.ts";
|
|
9
|
+
import type { AgentContext } from "./types.ts";
|
|
10
|
+
|
|
11
|
+
export async function runPostSetupHooks(
|
|
12
|
+
sb: Sandbox,
|
|
13
|
+
ctx: AgentContext,
|
|
14
|
+
hooks: readonly SandboxHook[] | undefined,
|
|
15
|
+
): Promise<Cleanup | void> {
|
|
16
|
+
if (!hooks?.length) return;
|
|
17
|
+
// 窄上下文与沙箱钩子同款:不把 session / model / telemetry 借给过程钩子。
|
|
18
|
+
const hookCtx: SandboxHookContext = {
|
|
19
|
+
experimentId: ctx.experimentId,
|
|
20
|
+
signal: ctx.signal,
|
|
21
|
+
progress: (update) => ctx.progress(update),
|
|
22
|
+
diagnostic: (input) => ctx.diagnostic(input),
|
|
23
|
+
};
|
|
24
|
+
const cleanups: Cleanup[] = [];
|
|
25
|
+
for (const hook of hooks) {
|
|
26
|
+
const cleanup = await hook(sb, hookCtx);
|
|
27
|
+
if (typeof cleanup === "function") cleanups.push(cleanup);
|
|
28
|
+
}
|
|
29
|
+
if (!cleanups.length) return;
|
|
30
|
+
return async () => {
|
|
31
|
+
for (const cleanup of [...cleanups].reverse()) await cleanup();
|
|
32
|
+
};
|
|
33
|
+
}
|
package/src/agents/types.ts
CHANGED
|
@@ -7,21 +7,42 @@ import type { StreamEvent, TraceSpan, Usage } from "../o11y/types.ts";
|
|
|
7
7
|
import type { Sandbox } from "../sandbox/types.ts";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* 见 docs/feature/adapters/architecture/coding-agent-extensions.md「类型边界」。
|
|
10
|
+
* 本地 stdio 形态的 MCP server:沙箱内起子进程,按 stdio 说 MCP 协议。
|
|
11
|
+
* 与 {@link McpHttpServer} 按形状判别(有 `command` 的是 stdio,有 `url` 的是 HTTP)。
|
|
13
12
|
*/
|
|
14
|
-
export interface
|
|
13
|
+
export interface McpStdioServer {
|
|
15
14
|
/** 服务器唯一名(config key)。 */
|
|
16
15
|
name: string;
|
|
17
16
|
/** 启动命令(如 "npx"、"node"、"uvx")。 */
|
|
18
17
|
command: string;
|
|
19
18
|
/** 传给命令的参数。 */
|
|
20
19
|
args?: string[];
|
|
21
|
-
/**
|
|
20
|
+
/** 注入服务器进程的环境变量(可能含 secret,不进 manifest)。 */
|
|
22
21
|
env?: Record<string, string>;
|
|
23
22
|
}
|
|
24
23
|
|
|
24
|
+
/**
|
|
25
|
+
* 远程 Streamable HTTP 形态的 MCP server:沙箱直接连一个 HTTP 端点。
|
|
26
|
+
* `url` 必须沙箱内可达——宿主机上的服务先经隧道(cloudflared / tailscale 等)暴露。
|
|
27
|
+
*/
|
|
28
|
+
export interface McpHttpServer {
|
|
29
|
+
/** 服务器唯一名(config key)。 */
|
|
30
|
+
name: string;
|
|
31
|
+
/** Streamable HTTP 端点(如 https://mem.example.com/mcp/)。 */
|
|
32
|
+
url: string;
|
|
33
|
+
/** 逐字写进每个请求的 HTTP 头(常用于 Authorization;可能含 secret,不进 manifest)。 */
|
|
34
|
+
headers?: Record<string, string>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* MCP server 描述符 —— 支持 MCP 的 adapter(Claude Code / Codex)共用的工具服务单元,
|
|
39
|
+
* 不是 native plugin 的一种。stdio 与 Streamable HTTP 两种形态按形状判别,不设 kind 标签
|
|
40
|
+
* (两种形态各有唯一必填判别字段);同时给出 `command` 与 `url` 属配置错误,setup 报错点名。
|
|
41
|
+
* 在 agent factory config 里声明,setup 阶段写进各自的配置文件。
|
|
42
|
+
* 见 docs/feature/adapters/architecture/coding-agent-extensions.md「类型边界」。
|
|
43
|
+
*/
|
|
44
|
+
export type McpServer = McpStdioServer | McpHttpServer;
|
|
45
|
+
|
|
25
46
|
/**
|
|
26
47
|
* Skill 的来源描述 —— Claude Code / Codex / Bub 共用的**数据类型**:只统一「从哪里取得
|
|
27
48
|
* 哪份 Skill」,安装位置、发现机制、要不要额外写 project instruction 由各 Adapter 决定。
|
|
@@ -67,8 +88,8 @@ export interface AgentSetupManifest {
|
|
|
67
88
|
/** 安装后 CLI 报告的版本;取不到时省略。 */
|
|
68
89
|
resolvedVersion?: string;
|
|
69
90
|
}>;
|
|
70
|
-
/** 挂上的 MCP server(不含 env
|
|
71
|
-
mcpServers?: Array<{ name: string; command: string; args?: string[] }>;
|
|
91
|
+
/** 挂上的 MCP server(只记非 secret 字段:stdio 不含 env,HTTP 不含 headers)。 */
|
|
92
|
+
mcpServers?: Array<{ name: string; command: string; args?: string[] } | { name: string; url: string }>;
|
|
72
93
|
/**
|
|
73
94
|
* 官方原生配置文件(Claude Code `settings.json` / Codex `config.toml`):只记 Agent 名、
|
|
74
95
|
* 项目相对来源路径与原始字节的 SHA-256,不落正文 —— 任意官方配置都可能携带敏感字符串,
|
package/src/cli.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { planCarry } from "./runner/fingerprint.ts";
|
|
|
17
17
|
import { failureDetailFromResult } from "./runner/feedback/failure.ts";
|
|
18
18
|
import { stopAllSandboxes, liveSandboxCount } from "./sandbox/registry.ts";
|
|
19
19
|
import { evalLevelStats } from "./shared/verdict.ts";
|
|
20
|
-
import {
|
|
20
|
+
import { prepareRunSandboxes, resolvedSandboxRecommendedConcurrency } from "./runner/sandbox-selection.ts";
|
|
21
21
|
import { Json, JUnit } from "./runner/reporters/json.ts";
|
|
22
22
|
import { Artifacts as ArtifactsReporter } from "./runner/reporters/artifacts.ts";
|
|
23
23
|
import {
|
|
@@ -178,7 +178,7 @@ const FLAG_OPTIONS = {
|
|
|
178
178
|
snapshot: { type: "string" },
|
|
179
179
|
/** `show` / `view` 命令专用:用文件默认导出的 `defineReport(...)` 替换两者共用的默认报告。 */
|
|
180
180
|
report: { type: "string" },
|
|
181
|
-
/** `show` / `view`
|
|
181
|
+
/** `show` / `view` 命令专用:选择报告的初始页;`show` 渲染该页并在尾部附其余页索引,`view` 以它作初始路由。未命中的页 id 按用法错误退出并列出可用页 id。 */
|
|
182
182
|
page: { type: "string" },
|
|
183
183
|
/** 只打印本次会匹配到的 eval × 运行配置,不实际执行(按下面 `--output` 选中的 profile 给出预览)。 */
|
|
184
184
|
dry: { type: "boolean" },
|
|
@@ -468,7 +468,7 @@ function evalsFilterFromExperiment(
|
|
|
468
468
|
): (id: string) => boolean {
|
|
469
469
|
const patternFilter = makeFilter(patterns);
|
|
470
470
|
let expFilter: (id: string) => boolean = () => true;
|
|
471
|
-
if (Array.isArray(evals)) expFilter = (
|
|
471
|
+
if (Array.isArray(evals)) expFilter = makeFilter(evals);
|
|
472
472
|
else if (typeof evals === "function") expFilter = evals;
|
|
473
473
|
return (id) => expFilter(id) && patternFilter(id);
|
|
474
474
|
}
|
|
@@ -771,6 +771,9 @@ async function main(): Promise<void> {
|
|
|
771
771
|
process.exit(1);
|
|
772
772
|
}
|
|
773
773
|
|
|
774
|
+
// environments 查表在 dry-run 与真实运行共用的规划边界完成;缺表项在任何沙箱/agent 花费前穷举失败。
|
|
775
|
+
prepareRunSandboxes(evals, agentRuns, config.sandbox);
|
|
776
|
+
|
|
774
777
|
if (flags.dry) {
|
|
775
778
|
// --dry 只按所选 profile 打印计划,不运行、不落盘 —— 三种 profile 各自的展示逻辑
|
|
776
779
|
// 都在 runner/feedback/{human,agent,ci}.ts 里,这里只负责拼数据、选函数、写流、退出。
|
|
@@ -826,7 +829,7 @@ async function main(): Promise<void> {
|
|
|
826
829
|
// envelope 展示的"携入"就会和 run.ts 真实调度的"携入"对不上(见 memory 的
|
|
827
830
|
// live-carry-row-shows-waiting-forever)。
|
|
828
831
|
const priorResults = flags.force ? undefined : await loadLatestResultsPerEval(join(cwd, ".niceeval"));
|
|
829
|
-
const carryPlan = priorResults?.length ? await planCarry(evals, agentRuns, priorResults) : undefined;
|
|
832
|
+
const carryPlan = priorResults?.length ? await planCarry(evals, agentRuns, priorResults, config.sandbox) : undefined;
|
|
830
833
|
const reusedFailures = (carryPlan?.carriedResults ?? [])
|
|
831
834
|
.map(failureDetailFromResult)
|
|
832
835
|
.filter((failure) => failure !== undefined);
|
|
@@ -834,8 +837,7 @@ async function main(): Promise<void> {
|
|
|
834
837
|
// 无全局默认:并发上限由 sandbox provider 的推荐值决定(多个 agentRun 各有 sandbox 时取
|
|
835
838
|
// 最小值,最保守的 provider 决定上限)。同一个值既进 RunFeedbackPlan.shape,也传给 runEvals——
|
|
836
839
|
// 两处必须是同一个数字,dashboard 展示的并发上限不能和真实调度的并发上限对不上。
|
|
837
|
-
const
|
|
838
|
-
const sandboxDefaultConcurrency = sandboxRecs.length > 0 ? Math.min(...sandboxRecs) : 10;
|
|
840
|
+
const sandboxDefaultConcurrency = resolvedSandboxRecommendedConcurrency(evals, agentRuns, config.sandbox);
|
|
839
841
|
const maxConcurrency =
|
|
840
842
|
flags.maxConcurrency ??
|
|
841
843
|
envNumber("NICEEVAL_MAX_CONCURRENCY") ??
|
package/src/define.ts
CHANGED
|
@@ -55,6 +55,9 @@ export function defineEval(def: EvalDef): EvalDef {
|
|
|
55
55
|
if (typeof def.test !== "function") {
|
|
56
56
|
throw new Error(t("define.evalTestRequired"));
|
|
57
57
|
}
|
|
58
|
+
if (def.environment !== undefined && def.environment.trim().length === 0) {
|
|
59
|
+
throw new Error(t("define.evalEnvironmentEmpty"));
|
|
60
|
+
}
|
|
58
61
|
return def;
|
|
59
62
|
}
|
|
60
63
|
|
package/src/i18n/en.ts
CHANGED
|
@@ -24,6 +24,8 @@ export const en = {
|
|
|
24
24
|
"skill.repoAmbiguous": "Repo skill {{source}} contains multiple skills; select which ones to enable with `skills: [...]`. Available: {{available}}.",
|
|
25
25
|
"skill.repoUnknownSkill": "Repo skill {{source}} (ref: {{ref}}) has no skill named \"{{skill}}\". Available: {{available}}.",
|
|
26
26
|
"skill.copyFailed": "Could not install skill \"{{name}}\" into {{dest}}:\n{{tail}}",
|
|
27
|
+
"mcp.ambiguousTransport":
|
|
28
|
+
"MCP server \"{{name}}\" specifies both \"command\" and \"url\" — pick one transport: \"command\" for a local stdio server, \"url\" for a remote Streamable HTTP endpoint.",
|
|
27
29
|
"plugin.marketplaceFailed": "Could not connect {{agent}} marketplace \"{{name}}\" (source: {{source}}, ref: {{ref}}):\n{{tail}}",
|
|
28
30
|
"plugin.marketplaceVerifyFailed": "Could not read back the registered marketplace list after adding {{agent}} marketplace \"{{name}}\" ({{command}}):\n{{tail}}",
|
|
29
31
|
"plugin.marketplaceNameMismatch":
|
|
@@ -83,8 +85,8 @@ export const en = {
|
|
|
83
85
|
" --diff[=file] sandbox workspace file-change summary; =file expands one file\n" +
|
|
84
86
|
" --history per experiment × eval execution timeline (mutually exclusive with --report)\n" +
|
|
85
87
|
" --results <dir> pin a results root --experiment <id> one experiment\n" +
|
|
86
|
-
" --report <file> custom report --page <id> pick
|
|
87
|
-
" reports
|
|
88
|
+
" --report <file> custom report --page <id> pick the initial page (multi-page\n" +
|
|
89
|
+
" reports render it, then list the rest as a page index with copyable commands)\n" +
|
|
88
90
|
" niceeval list list discovered evals\n" +
|
|
89
91
|
" niceeval view [eval-id-prefix…] [--out dir] [--port n] [--no-open]\n" +
|
|
90
92
|
" report pages + evidence rooms; --report <file> swaps in your report\n" +
|
|
@@ -151,6 +153,7 @@ export const en = {
|
|
|
151
153
|
"context.turnFailedDefault": "This send returned failed (turn status = failed)",
|
|
152
154
|
"define.agentNameRequired": "defineAgent requires name.",
|
|
153
155
|
"define.evalIdRejected": "defineEval does not accept id; ids are derived from file paths.",
|
|
156
|
+
"define.evalEnvironmentEmpty": "defineEval environment must be a non-empty profile id when provided.",
|
|
154
157
|
"define.evalTestRequired": "defineEval requires an async test(t) function.",
|
|
155
158
|
"define.experimentAgentRequired": "defineExperiment requires agent.",
|
|
156
159
|
"define.experimentFlagNotJson": "experiment.flags.{{key}} is not JSON-serializable (functions / undefined / cycles / bigint are not allowed); flags are persisted verbatim into result snapshots and must be plain JSON.",
|
package/src/i18n/zh-CN.ts
CHANGED
|
@@ -22,6 +22,8 @@ export const zhCN = {
|
|
|
22
22
|
"skill.repoAmbiguous": "repo skill {{source}} 里有多个 skill,必须用 `skills: [...]` 明确选择要启用哪些。可选:{{available}}。",
|
|
23
23
|
"skill.repoUnknownSkill": "repo skill {{source}}(ref: {{ref}})里没有名为 \"{{skill}}\" 的 skill。可选:{{available}}。",
|
|
24
24
|
"skill.copyFailed": "skill \"{{name}}\" 装进 {{dest}} 失败:\n{{tail}}",
|
|
25
|
+
"mcp.ambiguousTransport":
|
|
26
|
+
"MCP server \"{{name}}\" 同时给出了 command 和 url——二选一:本地 stdio 进程写 command,远程 Streamable HTTP 端点写 url。",
|
|
25
27
|
"plugin.marketplaceFailed": "{{agent}} marketplace \"{{name}}\" 连接失败(source: {{source}}, ref: {{ref}}):\n{{tail}}",
|
|
26
28
|
"plugin.marketplaceVerifyFailed": "{{agent}} marketplace \"{{name}}\" add 后回读注册列表失败({{command}}):\n{{tail}}",
|
|
27
29
|
"plugin.marketplaceNameMismatch":
|
|
@@ -79,7 +81,8 @@ export const zhCN = {
|
|
|
79
81
|
" --diff[=文件] agent 归因的文件改动摘要;=文件 按窗口展开单个文件\n" +
|
|
80
82
|
" --history 逐 experiment × eval 的执行时间轴(与 --report 互斥)\n" +
|
|
81
83
|
" --results <目录> 钉死结果根 --experiment <id> 只看该实验\n" +
|
|
82
|
-
" --report <文件> 自定义报告 --page <id>
|
|
84
|
+
" --report <文件> 自定义报告 --page <id> 定初始页(多页报告渲染该页,\n" +
|
|
85
|
+
" 尾部再附其余页索引)\n" +
|
|
83
86
|
" niceeval list 列出发现到的 eval\n" +
|
|
84
87
|
" niceeval view [eval-id 前缀…] [--out 目录] [--port n] [--no-open]\n" +
|
|
85
88
|
" 报告页 + 证据室;--report <文件> 整槽换成自定义报告(与 show 同一文件)\n" +
|
|
@@ -145,6 +148,7 @@ export const zhCN = {
|
|
|
145
148
|
"context.turnFailedDefault": "本轮 send 返回 failed(turn status = failed)",
|
|
146
149
|
"define.agentNameRequired": "defineAgent 需要 name。",
|
|
147
150
|
"define.evalIdRejected": "defineEval 不接受 id —— id 由文件路径推导。",
|
|
151
|
+
"define.evalEnvironmentEmpty": "defineEval 的 environment 如有提供,必须是非空的 profile id。",
|
|
148
152
|
"define.evalTestRequired": "defineEval 需要一个 async test(t) 函数。",
|
|
149
153
|
"define.experimentAgentRequired": "defineExperiment 需要 agent。",
|
|
150
154
|
"define.experimentFlagNotJson": "experiment.flags.{{key}} 不是可 JSON 序列化的值(函数 / undefined / 循环引用 / bigint 不允许);flags 会原样进入结果快照,必须是纯 JSON。",
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
// niceeval/report/built-in ——
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// 只是宿主默认拿哪个值的事实。
|
|
1
|
+
// niceeval/report/built-in —— 内建视图的家:每个内建视图是一份普通 defineReport 成品,
|
|
2
|
+
// 有自己的名字、一个源文件,按名字具名导出;默认导出恒等于 standard——裸宿主装载的那份
|
|
3
|
+
// (docs/feature/reports/library/built-in.md)。新增内建视图 = 新文件 + 新具名导出,
|
|
4
|
+
// 不需要注册表,也不改变装载管线。
|
|
6
5
|
|
|
7
|
-
import {
|
|
6
|
+
import { standard } from "./standard.tsx";
|
|
8
7
|
|
|
9
|
-
export
|
|
8
|
+
export { standard };
|
|
9
|
+
|
|
10
|
+
export default standard;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// standard —— 内建视图之一:报告 / Attempts / 追踪三页的通用结果站,没有任何私有钩子
|
|
2
|
+
// (docs/feature/reports/library/built-in.md)。裸 `niceeval show` 与 `niceeval view`
|
|
3
|
+
// 装载的就是它:一份普通 defineReport,与用户的 --report 文件同层、走同一条
|
|
4
|
+
// 装载 → resolve → validate → render 管线。「builtin」不是装载逻辑里的类别,
|
|
5
|
+
// 只是宿主默认拿哪个值的事实;用户报告经 defineReport({ extends: standard, … }) 整站复用。
|
|
6
|
+
//
|
|
7
|
+
// 三页站点:report(Hero + 警告 + 批量修复 prompt + 实验对比)、attempts(带过滤的
|
|
8
|
+
// attempt 全列表)、traces(执行瀑布)。裸宿主导航上能看到的一切内容都在这份定义里;
|
|
9
|
+
// 宿主保留的只有机器(docs/feature/reports/architecture.md「宿主保留的只有机器」)。
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
AttemptList,
|
|
13
|
+
Col,
|
|
14
|
+
CopyFixPrompt,
|
|
15
|
+
ExperimentComparison,
|
|
16
|
+
Hero,
|
|
17
|
+
ScopeWarnings,
|
|
18
|
+
TraceWaterfall,
|
|
19
|
+
defineReport,
|
|
20
|
+
} from "../index.ts";
|
|
21
|
+
|
|
22
|
+
export const standard = defineReport({
|
|
23
|
+
pages: [
|
|
24
|
+
{
|
|
25
|
+
id: "report",
|
|
26
|
+
title: { en: "Report", "zh-CN": "报告" },
|
|
27
|
+
content: (
|
|
28
|
+
<Col>
|
|
29
|
+
<Hero />
|
|
30
|
+
<ScopeWarnings />
|
|
31
|
+
<CopyFixPrompt />
|
|
32
|
+
<ExperimentComparison />
|
|
33
|
+
</Col>
|
|
34
|
+
),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "attempts",
|
|
38
|
+
title: "Attempts",
|
|
39
|
+
content: (
|
|
40
|
+
<Col>
|
|
41
|
+
<Hero />
|
|
42
|
+
<ScopeWarnings />
|
|
43
|
+
<AttemptList filter />
|
|
44
|
+
</Col>
|
|
45
|
+
),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "traces",
|
|
49
|
+
title: { en: "Traces", "zh-CN": "追踪" },
|
|
50
|
+
content: (
|
|
51
|
+
<Col>
|
|
52
|
+
<Hero />
|
|
53
|
+
<ScopeWarnings />
|
|
54
|
+
<TraceWaterfall />
|
|
55
|
+
</Col>
|
|
56
|
+
),
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
});
|
|
@@ -21,34 +21,42 @@ import {
|
|
|
21
21
|
type TextContext,
|
|
22
22
|
type WebContext,
|
|
23
23
|
} from "./tree.ts";
|
|
24
|
-
import type { ReportLocale } from "./locale.ts";
|
|
24
|
+
import type { LocalizedText, ReportLocale } from "./locale.ts";
|
|
25
25
|
import type { AttemptLocator } from "../results/locator.ts";
|
|
26
26
|
import type {
|
|
27
27
|
AttemptListItem,
|
|
28
|
+
CopyFixPromptData,
|
|
28
29
|
DeltaData,
|
|
29
30
|
EvalListItem,
|
|
30
31
|
ExperimentComparisonData,
|
|
31
32
|
ExperimentListItem,
|
|
33
|
+
HeroData,
|
|
32
34
|
LineData,
|
|
33
35
|
MatrixData,
|
|
34
36
|
ReportInput,
|
|
35
37
|
ScatterData,
|
|
36
38
|
ScopeSummaryData,
|
|
39
|
+
ScopeWarning,
|
|
37
40
|
ScoreboardData,
|
|
38
41
|
TableData,
|
|
42
|
+
TraceWaterfallRow,
|
|
39
43
|
} from "./types.ts";
|
|
40
44
|
import {
|
|
41
45
|
attemptListData,
|
|
46
|
+
copyFixPromptData,
|
|
42
47
|
deltaTableData,
|
|
43
48
|
evalListData,
|
|
44
49
|
experimentComparisonData,
|
|
45
50
|
experimentListData,
|
|
51
|
+
heroData,
|
|
46
52
|
metricLineData,
|
|
47
53
|
metricMatrixData,
|
|
48
54
|
metricScatterData,
|
|
49
55
|
metricTableData,
|
|
50
56
|
scopeSummaryData,
|
|
57
|
+
scopeWarningsData,
|
|
51
58
|
scoreboardData,
|
|
59
|
+
traceWaterfallData,
|
|
52
60
|
type DeltaTableOptions,
|
|
53
61
|
type MetricLineOptions,
|
|
54
62
|
type MetricMatrixOptions,
|
|
@@ -64,18 +72,26 @@ import {
|
|
|
64
72
|
experimentComparisonText,
|
|
65
73
|
experimentListText,
|
|
66
74
|
barsText,
|
|
75
|
+
heroCardText,
|
|
67
76
|
lineText,
|
|
68
77
|
matrixText,
|
|
69
78
|
scatterText,
|
|
79
|
+
scopeWarningsText,
|
|
70
80
|
scoreboardText,
|
|
71
81
|
scopeSummaryText,
|
|
72
82
|
tableText,
|
|
83
|
+
traceWaterfallText,
|
|
73
84
|
} from "./text/faces.ts";
|
|
74
85
|
import { ScopeSummary as ScopeSummaryWeb } from "./react/ScopeSummary.tsx";
|
|
75
86
|
import { ExperimentComparisonView } from "./react/ExperimentComparison.tsx";
|
|
76
87
|
import { ExperimentList as ExperimentListWeb } from "./react/ExperimentList.tsx";
|
|
77
88
|
import { EvalList as EvalListWeb } from "./react/EvalList.tsx";
|
|
78
89
|
import { AttemptList as AttemptListWeb } from "./react/AttemptList.tsx";
|
|
90
|
+
import { HeroCard as HeroCardWeb } from "./react/HeroCard.tsx";
|
|
91
|
+
import { PoweredBy as PoweredByWeb } from "./react/PoweredBy.tsx";
|
|
92
|
+
import { ScopeWarnings as ScopeWarningsWeb } from "./react/ScopeWarnings.tsx";
|
|
93
|
+
import { CopyFixPrompt as CopyFixPromptWeb } from "./react/CopyFixPrompt.tsx";
|
|
94
|
+
import { TraceWaterfall as TraceWaterfallWeb } from "./react/TraceWaterfall.tsx";
|
|
79
95
|
import { MetricTable as MetricTableWeb } from "./react/MetricTable.tsx";
|
|
80
96
|
import { MetricMatrix as MetricMatrixWeb } from "./react/MetricMatrix.tsx";
|
|
81
97
|
import { MetricBars as MetricBarsWeb } from "./react/MetricBars.tsx";
|
|
@@ -257,6 +273,55 @@ const validateAttemptListData: Validator = (data) => {
|
|
|
257
273
|
return null;
|
|
258
274
|
};
|
|
259
275
|
|
|
276
|
+
const validateHeroData: Validator = (data) => {
|
|
277
|
+
if (!isObject(data)) return "expected an object";
|
|
278
|
+
if (!("latestStartedAt" in data) || (data.latestStartedAt !== null && typeof data.latestStartedAt !== "string")) {
|
|
279
|
+
return 'missing "latestStartedAt" (string | null)';
|
|
280
|
+
}
|
|
281
|
+
if (typeof data.snapshots !== "number") return 'missing "snapshots" (number)';
|
|
282
|
+
return null;
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const validateScopeWarningsData: Validator = (data) => {
|
|
286
|
+
if (!Array.isArray(data)) return "expected an array of ScopeWarning";
|
|
287
|
+
for (const item of data as unknown[]) {
|
|
288
|
+
if (!isObject(item) || typeof item.kind !== "string" || typeof item.message !== "string") {
|
|
289
|
+
return "each warning needs { kind, message, … }";
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return null;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const validateCopyFixPromptData: Validator = (data) => {
|
|
296
|
+
if (!isObject(data)) return "expected an object";
|
|
297
|
+
if (typeof data.prompt !== "string") return 'missing "prompt" (string)';
|
|
298
|
+
if (typeof data.failures !== "number") return 'missing "failures" (number)';
|
|
299
|
+
return null;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const validateTraceWaterfallData: Validator = (data) => {
|
|
303
|
+
if (!Array.isArray(data)) return "expected an array of TraceWaterfallRow";
|
|
304
|
+
for (const row of data as unknown[]) {
|
|
305
|
+
if (
|
|
306
|
+
!isObject(row) ||
|
|
307
|
+
typeof row.experimentId !== "string" ||
|
|
308
|
+
typeof row.evalId !== "string" ||
|
|
309
|
+
typeof row.locator !== "string" ||
|
|
310
|
+
!("durationMs" in row) ||
|
|
311
|
+
(row.durationMs !== null && typeof row.durationMs !== "number") ||
|
|
312
|
+
!Array.isArray(row.spans)
|
|
313
|
+
) {
|
|
314
|
+
return "each row needs { experimentId, evalId, locator, durationMs: number | null, spans }";
|
|
315
|
+
}
|
|
316
|
+
for (const span of row.spans as unknown[]) {
|
|
317
|
+
if (!isObject(span) || typeof span.name !== "string" || typeof span.startOffsetMs !== "number") {
|
|
318
|
+
return "each span needs { name, kind, startOffsetMs, durationMs, failed }";
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return null;
|
|
323
|
+
};
|
|
324
|
+
|
|
260
325
|
// ───────────────────────── spec / data 双形态的通用装配 ─────────────────────────
|
|
261
326
|
|
|
262
327
|
interface DataComponentDef<Data, Options, Presentation> {
|
|
@@ -463,6 +528,8 @@ export type AttemptListProps = DataProps<
|
|
|
463
528
|
EntityListChrome & {
|
|
464
529
|
/** 过滤 / 截断前的总数;省略时等于 data 长度。 */
|
|
465
530
|
total?: number;
|
|
531
|
+
/** web 面加过滤输入框(按 experiment、eval、agent、verdict 或摘要文本收窄行);渐进增强,不改变数据与 text 面。 */
|
|
532
|
+
filter?: boolean;
|
|
466
533
|
}
|
|
467
534
|
>;
|
|
468
535
|
|
|
@@ -470,7 +537,7 @@ export type AttemptListProps = DataProps<
|
|
|
470
537
|
export const AttemptList = makeDataComponent<
|
|
471
538
|
readonly AttemptListItem[],
|
|
472
539
|
Record<never, never>,
|
|
473
|
-
EntityListChrome & { total?: number }
|
|
540
|
+
EntityListChrome & { total?: number; filter?: boolean }
|
|
474
541
|
>({
|
|
475
542
|
name: "AttemptList",
|
|
476
543
|
dataFnName: "attemptListData",
|
|
@@ -482,6 +549,7 @@ export const AttemptList = makeDataComponent<
|
|
|
482
549
|
<AttemptListWeb
|
|
483
550
|
data={props.data}
|
|
484
551
|
total={props.total}
|
|
552
|
+
filter={props.filter}
|
|
485
553
|
locale={props.locale ?? ctx.locale}
|
|
486
554
|
attemptHref={hrefOf(props, ctx) ?? ctx.attemptHref}
|
|
487
555
|
className={props.className}
|
|
@@ -537,6 +605,154 @@ export const FailureList = defineComponent<FailureListProps>(async (props, ctx)
|
|
|
537
605
|
});
|
|
538
606
|
FailureList.displayName = "FailureList";
|
|
539
607
|
|
|
608
|
+
// ───────────────────────── 站点组件(Hero / PoweredBy / ScopeWarnings / CopyFixPrompt / TraceWaterfall)─────────────────────────
|
|
609
|
+
|
|
610
|
+
/** `Hero` 的 props:标题缺省取 `ctx.report.title`(回退链后的站点标题)。 */
|
|
611
|
+
export interface HeroProps {
|
|
612
|
+
/** 覆盖标题;省略时取 ctx.report.title(回退链后的站点标题)。 */
|
|
613
|
+
title?: LocalizedText;
|
|
614
|
+
className?: string;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/** HeroCard 的 data 校验入口(它不经 makeDataComponent,数据形态是唯一形态)。 */
|
|
618
|
+
const assertHeroData = (data: unknown): HeroData => {
|
|
619
|
+
const problem = validateHeroData(data);
|
|
620
|
+
if (problem !== null) throw dataShapeError("HeroCard", "heroData", "HeroData", problem);
|
|
621
|
+
return data as HeroData;
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* `HeroCard`:Hero 的渲染件,双面组件,只收 data 形态——标题输入是站点声明与 Scope 的
|
|
626
|
+
* 合成物,没有单独的 spec 等价形。web 面渲染 hero 标题(h1)、按渲染 locale 格式化的运行
|
|
627
|
+
* meta(latestStartedAt 为 null 时内置「暂无运行」文案)与品牌行(等同 PoweredBy,恒含、
|
|
628
|
+
* 无拆除 prop);text 面输出标题行与 meta 行,不含品牌行
|
|
629
|
+
* (docs/feature/reports/library/site-components.md「HeroCard」)。
|
|
630
|
+
*/
|
|
631
|
+
export const HeroCard = defineComponent<HeroCardProps>({
|
|
632
|
+
web: (props, ctx) => {
|
|
633
|
+
assertHeroData(props.data);
|
|
634
|
+
return <HeroCardWeb title={props.title} data={props.data} className={props.className} locale={ctx.locale} />;
|
|
635
|
+
},
|
|
636
|
+
text: (props, ctx) => {
|
|
637
|
+
assertHeroData(props.data);
|
|
638
|
+
return heroCardText(props.title, props.data, ctx);
|
|
639
|
+
},
|
|
640
|
+
});
|
|
641
|
+
HeroCard.displayName = "HeroCard";
|
|
642
|
+
|
|
643
|
+
/** `HeroCard` 的 props:标题 + `heroData()` 的产物,只有 data 形态。 */
|
|
644
|
+
export interface HeroCardProps {
|
|
645
|
+
title: LocalizedText;
|
|
646
|
+
data: HeroData;
|
|
647
|
+
className?: string;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* `Hero`:页首的站点标题区——标题、最后运行时间、快照合成来源,恒含品牌行。官方组合组件,
|
|
652
|
+
* 与手写 `<HeroCard title={title ?? ctx.report.title} data={await heroData(ctx.scope)} />`
|
|
653
|
+
* 严格等价、没有私有能力;读 `ctx.report` 意味着输出跟随站点,要站点无关的标题区直接用
|
|
654
|
+
* `HeroCard` 显式传值(docs/feature/reports/library/site-components.md「Hero」)。
|
|
655
|
+
*/
|
|
656
|
+
export const Hero = defineComponent<HeroProps>(async ({ title, className }, ctx) => (
|
|
657
|
+
<HeroCard title={title ?? ctx.report.title} data={await heroData(ctx.scope)} className={className} />
|
|
658
|
+
));
|
|
659
|
+
Hero.displayName = "Hero";
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* `PoweredBy`:唯一的品牌件,无 props 双面组件。web 面渲染指向 niceeval 官网的一行品牌色
|
|
663
|
+
* 小字(`utm_source=report&utm_medium=powered-by`,`rel` 仅 `noopener` 以保留 Referer);
|
|
664
|
+
* text 面零输出。没有任何配置——品牌契约是「提供一个组件,不给开关」:不想要品牌就不用
|
|
665
|
+
* 这些组件、自己写替代组件(docs/feature/reports/library/site-components.md「PoweredBy」)。
|
|
666
|
+
*/
|
|
667
|
+
export const PoweredBy = defineComponent<Record<never, never>>({
|
|
668
|
+
web: () => <PoweredByWeb />,
|
|
669
|
+
text: () => "",
|
|
670
|
+
});
|
|
671
|
+
PoweredBy.displayName = "PoweredBy";
|
|
672
|
+
|
|
673
|
+
/** `ScopeWarnings` 的 props:spec 形态取宿主 Scope 的 warnings,data 形态收 `ScopeWarning[]`。 */
|
|
674
|
+
export type ScopeWarningsProps = DataProps<readonly ScopeWarning[], Record<never, never>, ChromeProps>;
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* `ScopeWarnings`:选择警告区,警告的唯一呈现组件。把 Scope 携带的 `ScopeWarning[]`
|
|
678
|
+
* 按「下一步动作」聚合渲染(带 experimentId 的按实验聚合、非实验作用域按 kind 聚合;
|
|
679
|
+
* integrity 组在前);web 面组头带去重后的可复制命令、明细收原生 `<details>`(总条数 ≤ 3
|
|
680
|
+
* 默认展开),text 面同构但不折叠。空警告集与裸 `Snapshot[]` 输入两面零输出
|
|
681
|
+
* (docs/feature/reports/library/site-components.md「ScopeWarnings」)。
|
|
682
|
+
*/
|
|
683
|
+
export const ScopeWarnings = makeDataComponent<readonly ScopeWarning[], Record<never, never>, ChromeProps>({
|
|
684
|
+
name: "ScopeWarnings",
|
|
685
|
+
dataFnName: "scopeWarningsData",
|
|
686
|
+
shapeName: "ScopeWarning[]",
|
|
687
|
+
dataFn: (input) => scopeWarningsData(input),
|
|
688
|
+
specKeys: [],
|
|
689
|
+
validate: validateScopeWarningsData,
|
|
690
|
+
web: (props, ctx) =>
|
|
691
|
+
props.data.length === 0 ? null : (
|
|
692
|
+
<ScopeWarningsWeb data={props.data} locale={props.locale ?? ctx.locale} className={props.className} />
|
|
693
|
+
),
|
|
694
|
+
text: (props, ctx) => scopeWarningsText(props.data, ctx),
|
|
695
|
+
}) as unknown as ReportComponent<ScopeWarningsProps>;
|
|
696
|
+
|
|
697
|
+
/** `CopyFixPrompt` 的 props:spec 形态无选项,data 形态收 `copyFixPromptData()` 的产物。 */
|
|
698
|
+
export type CopyFixPromptProps = DataProps<CopyFixPromptData, Record<never, never>, ChromeProps>;
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* `CopyFixPrompt`:把当前范围的全部失败整理成一段可交给 coding agent 的修复 prompt。
|
|
702
|
+
* prompt 在 resolve 阶段算好、烘进静态 HTML,无 JS 时在折叠块里完整可读,「复制」是增强层
|
|
703
|
+
* 行为;`failures` 为 0 时两面零输出;text 面恒零输出——终端里的等价能力是 `show` 的
|
|
704
|
+
* attempt 下钻命令本身(docs/feature/reports/library/site-components.md「CopyFixPrompt」)。
|
|
705
|
+
*/
|
|
706
|
+
export const CopyFixPrompt = makeDataComponent<CopyFixPromptData, Record<never, never>, ChromeProps>({
|
|
707
|
+
name: "CopyFixPrompt",
|
|
708
|
+
dataFnName: "copyFixPromptData",
|
|
709
|
+
shapeName: "CopyFixPromptData",
|
|
710
|
+
dataFn: (input) => copyFixPromptData(input),
|
|
711
|
+
specKeys: [],
|
|
712
|
+
validate: validateCopyFixPromptData,
|
|
713
|
+
web: (props, ctx) =>
|
|
714
|
+
props.data.failures === 0 ? null : (
|
|
715
|
+
<CopyFixPromptWeb data={props.data} locale={props.locale ?? ctx.locale} className={props.className} />
|
|
716
|
+
),
|
|
717
|
+
text: () => "",
|
|
718
|
+
}) as unknown as ReportComponent<CopyFixPromptProps>;
|
|
719
|
+
|
|
720
|
+
/** `TraceWaterfall` 的 props:spec 形态无选项,data 形态收 `traceWaterfallData()` 的产物。 */
|
|
721
|
+
export type TraceWaterfallProps = DataProps<
|
|
722
|
+
readonly TraceWaterfallRow[],
|
|
723
|
+
Record<never, never>,
|
|
724
|
+
ChromeProps & { attemptHref?: (locator: AttemptLocator) => string }
|
|
725
|
+
>;
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* `TraceWaterfall`:每个 attempt 一行的执行时间瀑布,用 canonical OTel 字段显示被测 agent
|
|
729
|
+
* 的原始 span(agent / model / tool)。web 面静态渲染顶层 span 分解条(失败 span 带失败
|
|
730
|
+
* 标记),行链接 attempt 详情;text 面每 attempt 一行(locator、总耗时、span 计数与失败
|
|
731
|
+
* 标记)+ 可复制的 `--timing` 下钻命令。trace 缺失的行照常出现并如实显示缺失;runner
|
|
732
|
+
* 生命周期节点不进瀑布(docs/feature/reports/library/site-components.md「TraceWaterfall」)。
|
|
733
|
+
*/
|
|
734
|
+
export const TraceWaterfall = makeDataComponent<
|
|
735
|
+
readonly TraceWaterfallRow[],
|
|
736
|
+
Record<never, never>,
|
|
737
|
+
ChromeProps & { attemptHref?: (locator: AttemptLocator) => string }
|
|
738
|
+
>({
|
|
739
|
+
name: "TraceWaterfall",
|
|
740
|
+
dataFnName: "traceWaterfallData",
|
|
741
|
+
shapeName: "TraceWaterfallRow[]",
|
|
742
|
+
dataFn: (input) => traceWaterfallData(input),
|
|
743
|
+
specKeys: [],
|
|
744
|
+
validate: validateTraceWaterfallData,
|
|
745
|
+
web: (props, ctx) => (
|
|
746
|
+
<TraceWaterfallWeb
|
|
747
|
+
data={props.data}
|
|
748
|
+
attemptHref={hrefOf(props, ctx) ?? ctx.attemptHref}
|
|
749
|
+
locale={props.locale ?? ctx.locale}
|
|
750
|
+
className={props.className}
|
|
751
|
+
/>
|
|
752
|
+
),
|
|
753
|
+
text: (props, ctx) => traceWaterfallText(props.data, ctx),
|
|
754
|
+
}) as unknown as ReportComponent<TraceWaterfallProps>;
|
|
755
|
+
|
|
540
756
|
// ───────────────────────── 指标组件 ─────────────────────────
|
|
541
757
|
|
|
542
758
|
export type MetricTableProps = DataProps<
|