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
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// cases: docs/engineering/unit-tests/reports/cases.md
|
|
2
|
+
// 「外壳、页面与 Tabs」分区——
|
|
3
|
+
// 品牌位恒为 NiceEval 字标(声明 title 也不覆盖)、hero 走标题回退链(与浏览器标题同源;
|
|
4
|
+
// document.title 由 useEffect 设置,静态渲染不执行,这里断言 hero 即断言同一个 shellTitle)、
|
|
5
|
+
// ReportLink.icon 渲染在 label 前(web 面)。契约:docs/feature/reports/library/shell.md「行为约束」。
|
|
6
|
+
|
|
7
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
8
|
+
import { beforeAll, describe, expect, it } from "vitest";
|
|
9
|
+
import { App } from "./App.tsx";
|
|
10
|
+
import type { ViewData } from "./types.ts";
|
|
11
|
+
|
|
12
|
+
beforeAll(() => {
|
|
13
|
+
// App 的初始状态读 location(hash 路由 / 旧版 ?modal=);node 下静态渲染补最小 stub。
|
|
14
|
+
(globalThis as { location?: unknown }).location = { hash: "", search: "", pathname: "/" };
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const reportPages = { report: { en: "<p>REPORT_BODY</p>", "zh-CN": "<p>REPORT_BODY</p>" } };
|
|
18
|
+
|
|
19
|
+
function dataWithShell(report: ViewData["report"]): ViewData {
|
|
20
|
+
return { composedRuns: 1, snapshots: [], ...(report !== undefined ? { report } : {}) };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("外壳:品牌位、hero 标题与 ReportLink.icon", () => {
|
|
24
|
+
it("声明 title 后品牌位仍是 NiceEval 字标;hero 显示走完回退链的报告标题", () => {
|
|
25
|
+
const html = renderToStaticMarkup(
|
|
26
|
+
<App
|
|
27
|
+
data={dataWithShell({
|
|
28
|
+
title: { en: "Memory Evals", "zh-CN": "记忆能力评测" },
|
|
29
|
+
links: [],
|
|
30
|
+
pages: [{ id: "report", title: "Report" }],
|
|
31
|
+
initialPageId: "report",
|
|
32
|
+
})}
|
|
33
|
+
reportPages={reportPages}
|
|
34
|
+
/>,
|
|
35
|
+
);
|
|
36
|
+
// 品牌位:恒定的 NiceEval 字标,不吃报告 title。
|
|
37
|
+
const brand = html.match(/class="brand"[\s\S]*?<\/a>/)![0];
|
|
38
|
+
expect(brand).toContain(">NiceEval</span>");
|
|
39
|
+
expect(brand).not.toContain("Memory Evals");
|
|
40
|
+
// hero:报告 title(node 环境 locale 回退 en)。
|
|
41
|
+
const hero = html.match(/<h1>[\s\S]*?<\/h1>/)![0];
|
|
42
|
+
expect(hero).toContain("Memory Evals");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("缺外壳声明(旧数据)时 hero 落内置文案 Eval Results,品牌位不变", () => {
|
|
46
|
+
const html = renderToStaticMarkup(<App data={dataWithShell(undefined)} reportPages={reportPages} />);
|
|
47
|
+
expect(html.match(/<h1>[\s\S]*?<\/h1>/)![0]).toContain("Eval Results");
|
|
48
|
+
expect(html.match(/class="brand"[\s\S]*?<\/a>/)![0]).toContain(">NiceEval</span>");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("ReportLink.icon 的内联 SVG 渲染在 label 前,原样内联", () => {
|
|
52
|
+
const html = renderToStaticMarkup(
|
|
53
|
+
<App
|
|
54
|
+
data={dataWithShell({
|
|
55
|
+
title: "T",
|
|
56
|
+
links: [{ label: "GitHub", href: "https://example.com", icon: { svg: '<svg data-mark="gh"></svg>' } }],
|
|
57
|
+
pages: [{ id: "report", title: "Report" }],
|
|
58
|
+
initialPageId: "report",
|
|
59
|
+
})}
|
|
60
|
+
reportPages={reportPages}
|
|
61
|
+
/>,
|
|
62
|
+
);
|
|
63
|
+
const link = html.match(/<a[^>]*href="https:\/\/example\.com"[\s\S]*?<\/a>/)![0];
|
|
64
|
+
const iconAt = link.indexOf('<svg data-mark="gh"></svg>');
|
|
65
|
+
const labelAt = link.indexOf("GitHub");
|
|
66
|
+
expect(iconAt).toBeGreaterThan(-1);
|
|
67
|
+
expect(labelAt).toBeGreaterThan(iconAt);
|
|
68
|
+
});
|
|
69
|
+
});
|
package/src/view/app/App.tsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { detectLocale, makeTranslator, persistLocale, setDocumentLocale } from "./i18n.ts";
|
|
3
|
-
import type {
|
|
4
|
-
import type { Locale, LocalizedText, ReportSlotHtml, Tab, ViewData, ViewResult } from "./types.ts";
|
|
3
|
+
import type { Locale, LocalizedText, ReportSlotHtml, Tab, ViewData, ViewReportPageMeta, ViewResult } from "./types.ts";
|
|
5
4
|
import { flattenAttempts, resultFromUrl } from "./lib/rows.ts";
|
|
6
5
|
import { parseAttemptHash, resolveAttemptLocator, unresolvedAttemptWarning } from "./lib/attempt-route.ts";
|
|
7
6
|
import { formatDateTime } from "./lib/format.ts";
|
|
@@ -9,22 +8,34 @@ import { CopyFixPrompt } from "./components/CopyControls.tsx";
|
|
|
9
8
|
import { SkippedRunsBanner } from "./components/SkippedRunsBanner.tsx";
|
|
10
9
|
import { AttemptModal } from "./components/AttemptModal.tsx";
|
|
11
10
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./components/ui/tabs.tsx";
|
|
12
|
-
import {
|
|
11
|
+
import { AttemptsView } from "./pages/AttemptsPage.tsx";
|
|
13
12
|
import { TracesView } from "./pages/TracesPage.tsx";
|
|
14
13
|
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
// 导航组成固定(docs/feature/reports/view.md「页面构成」):报告页按声明顺序在前
|
|
15
|
+
// (路由 `#/page/<id>`,`--page <id>` 定初始页),内置的 Attempts、Traces 证据页恒排在
|
|
16
|
+
// 报告页之后——证据页由宿主拥有,报告定义不能移除或重排它们。
|
|
17
|
+
// 报告页的 tab 值带 `page:` 前缀,避免与证据页 id(attempts / traces)撞名。
|
|
18
|
+
const EVIDENCE_TABS: { id: Tab; label: "nav.attempts" | "nav.traces" }[] = [
|
|
19
|
+
{ id: "attempts", label: "nav.attempts" },
|
|
20
20
|
{ id: "traces", label: "nav.traces" },
|
|
21
21
|
];
|
|
22
22
|
|
|
23
|
-
/**
|
|
23
|
+
/** niceeval 官网;web 面 hero 下方恒含指向它的 `Powered by NiceEval` 一行,无关闭配置。 */
|
|
24
|
+
const NICEEVAL_SITE_URL = "https://niceeval.com";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* LocalizedText 的确定回退(docs/feature/reports/library/shell.md):当前 locale → en →
|
|
28
|
+
* 按 locale 键字典序的第一个非空值。字符串原样返回。
|
|
29
|
+
*/
|
|
24
30
|
export function localizedText(text: LocalizedText | undefined, locale: Locale): string | undefined {
|
|
25
31
|
if (!text) return undefined;
|
|
26
32
|
if (typeof text === "string") return text;
|
|
27
|
-
|
|
33
|
+
if (text[locale]) return text[locale];
|
|
34
|
+
if (text.en) return text.en;
|
|
35
|
+
for (const key of Object.keys(text).sort()) {
|
|
36
|
+
if (text[key]) return text[key];
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
/** 初始 URL → 直接打开的 attempt:先认 #/attempt/@<locator> 深链,回退旧版 ?modal= 参数。 */
|
|
@@ -40,12 +51,43 @@ function modalResultFromLocation(snapshots: ViewData["snapshots"]): ViewResult |
|
|
|
40
51
|
return resultFromUrl(snapshots);
|
|
41
52
|
}
|
|
42
53
|
|
|
43
|
-
|
|
54
|
+
/**
|
|
55
|
+
* 报告槽:React 19 对 dangerouslySetInnerHTML 只比较对象身份,身份一变就无条件重设
|
|
56
|
+
* innerHTML(不再比对 __html 字符串值)。{__html} 必须 memo 住,否则 App 任意一次重渲染
|
|
57
|
+
* (开关 attempt 弹窗、切语言)都会重建槽内 DOM,丢掉用户展开的 <details>、排序和过滤状态。
|
|
58
|
+
*/
|
|
59
|
+
function ReportSlot({ html }: { html: string }) {
|
|
60
|
+
const markup = useMemo(() => ({ __html: html }), [html]);
|
|
61
|
+
return <div className="report-slot" dangerouslySetInnerHTML={markup} />;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** `#/page/<id>` / `#/attempts` / `#/traces` → tab 值;认不出返回 null(交给初始页兜底)。 */
|
|
65
|
+
function tabFromHash(hash: string, pages: ViewReportPageMeta[]): Tab | null {
|
|
66
|
+
const pageMatch = /^#\/page\/([a-z0-9-]+)$/.exec(hash);
|
|
67
|
+
if (pageMatch && pages.some((p) => p.id === pageMatch[1])) return `page:${pageMatch[1]}`;
|
|
68
|
+
if (hash === "#/attempts") return "attempts";
|
|
69
|
+
if (hash === "#/traces") return "traces";
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** tab 值 → hash 路由(报告页 `#/page/<id>`,证据页 `#/attempts` / `#/traces`)。 */
|
|
74
|
+
function hashForTab(tab: Tab): string {
|
|
75
|
+
return tab.startsWith("page:") ? `#/page/${tab.slice("page:".length)}` : `#/${tab}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function App({ data, reportPages }: { data: ViewData; reportPages: Record<string, ReportSlotHtml> }) {
|
|
44
79
|
const snapshots = data.snapshots ?? [];
|
|
45
80
|
const attempts = useMemo(() => flattenAttempts(snapshots), [snapshots]);
|
|
46
81
|
const [locale, setLocale] = useState<Locale>(() => detectLocale());
|
|
47
82
|
const t = useMemo(() => makeTranslator(locale), [locale]);
|
|
48
|
-
|
|
83
|
+
|
|
84
|
+
// 报告声明缺省(旧数据 / 空烘焙)时按单页 `report` 兜底,页名用内置页名。
|
|
85
|
+
const pages: ViewReportPageMeta[] = data.report?.pages?.length
|
|
86
|
+
? data.report.pages
|
|
87
|
+
: [{ id: "report", title: { en: "Report", "zh-CN": "报告" } }];
|
|
88
|
+
const initialPageId = data.report?.initialPageId ?? pages[0]!.id;
|
|
89
|
+
|
|
90
|
+
const [tab, setTab] = useState<Tab>(() => tabFromHash(location.hash, pages) ?? `page:${initialPageId}`);
|
|
49
91
|
const [modalResult, setModalResult] = useState<ViewResult | null>(() => modalResultFromLocation(snapshots));
|
|
50
92
|
// 当前 modal 的 hash 历史条目前面是否还有本页条目(本页 push 的 / 前进键回到的):
|
|
51
93
|
// true → UI 关闭走 history.back(),前进键还能重新打开;false(深链直接落地)→ 原地抹 hash,
|
|
@@ -57,6 +99,14 @@ export function App({ data, reportHtml }: { data: ViewData; reportHtml: ReportSl
|
|
|
57
99
|
persistLocale(locale);
|
|
58
100
|
}, [locale]);
|
|
59
101
|
|
|
102
|
+
// 首页 hero 与浏览器标题跟随外壳标题(回退链在 server 侧走完:def.title → 唯一快照 name →
|
|
103
|
+
// 内置文案「Eval 运行结果 / Eval Results」);缺声明(旧数据)时按内置文案兜底。
|
|
104
|
+
// 页头品牌位不归它——那里是恒定的 NiceEval 字标。
|
|
105
|
+
const shellTitle = localizedText(data.report?.title, locale) ?? t("hero.title");
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
document.title = shellTitle;
|
|
108
|
+
}, [shellTitle]);
|
|
109
|
+
|
|
60
110
|
const closeModal = useCallback(() => {
|
|
61
111
|
setModalResult(null);
|
|
62
112
|
if (modalOwnsHistory.current) {
|
|
@@ -72,43 +122,77 @@ export function App({ data, reportHtml }: { data: ViewData; reportHtml: ReportSl
|
|
|
72
122
|
}
|
|
73
123
|
}, []);
|
|
74
124
|
|
|
75
|
-
// 浏览器前进/后退、手改 hash
|
|
125
|
+
// 浏览器前进/后退、手改 hash、页内链接(attempt 深链与 `#/page/<id>` 页路由)统一从
|
|
126
|
+
// hashchange 分发:attempt hash 开证据室弹窗,页/证据室 hash 切当前 tab。
|
|
76
127
|
useEffect(() => {
|
|
77
128
|
const onHashChange = () => {
|
|
78
129
|
const locator = parseAttemptHash(location.hash);
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
|
|
130
|
+
if (locator) {
|
|
131
|
+
const found = resolveAttemptLocator(snapshots, locator);
|
|
132
|
+
if (!found) {
|
|
133
|
+
console.warn(unresolvedAttemptWarning(location.hash));
|
|
134
|
+
setModalResult(null);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// 经浏览器导航打开:前一条历史仍是本页,UI 关闭可以安全 back()。
|
|
138
|
+
modalOwnsHistory.current = true;
|
|
139
|
+
setModalResult(found);
|
|
82
140
|
return;
|
|
83
141
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
// 经浏览器导航打开:前一条历史仍是本页,UI 关闭可以安全 back()。
|
|
91
|
-
modalOwnsHistory.current = true;
|
|
92
|
-
setModalResult(found);
|
|
142
|
+
modalOwnsHistory.current = false;
|
|
143
|
+
setModalResult(null);
|
|
144
|
+
const routed = tabFromHash(location.hash, pages);
|
|
145
|
+
if (routed) setTab(routed);
|
|
93
146
|
};
|
|
94
147
|
window.addEventListener("hashchange", onHashChange);
|
|
95
148
|
return () => window.removeEventListener("hashchange", onHashChange);
|
|
96
|
-
}, [snapshots]);
|
|
149
|
+
}, [snapshots, pages]);
|
|
150
|
+
|
|
151
|
+
const selectTab = useCallback((value: Tab) => {
|
|
152
|
+
setTab(value);
|
|
153
|
+
try {
|
|
154
|
+
history.replaceState(null, "", hashForTab(value));
|
|
155
|
+
} catch {
|
|
156
|
+
// 更新路由失败不影响切页。
|
|
157
|
+
}
|
|
158
|
+
}, []);
|
|
159
|
+
|
|
160
|
+
const footerText = localizedText(data.report?.footer, locale);
|
|
97
161
|
|
|
98
162
|
return (
|
|
99
|
-
<Tabs value={tab} onValueChange={(v) =>
|
|
163
|
+
<Tabs value={tab} onValueChange={(v) => selectTab(v as Tab)}>
|
|
100
164
|
<header className="topbar">
|
|
101
|
-
|
|
165
|
+
{/* 页头左端是恒定的 NiceEval 品牌字标(与 Powered by 行同族的产品品牌位),
|
|
166
|
+
报告定义不能覆盖或移除;报告 title 的落点是下方 hero 与浏览器标题。 */}
|
|
167
|
+
<a className="brand" href={hashForTab(`page:${initialPageId}`)}>
|
|
102
168
|
<span className="mark" />
|
|
103
169
|
<span>NiceEval</span>
|
|
104
170
|
</a>
|
|
105
171
|
<TabsList aria-label={t("nav.label")}>
|
|
106
|
-
{
|
|
172
|
+
{pages.map((page) => (
|
|
173
|
+
<TabsTrigger key={`page:${page.id}`} value={`page:${page.id}`}>
|
|
174
|
+
{localizedText(page.title, locale) ?? page.id}
|
|
175
|
+
</TabsTrigger>
|
|
176
|
+
))}
|
|
177
|
+
{EVIDENCE_TABS.map((item) => (
|
|
107
178
|
<TabsTrigger key={item.id} value={item.id}>
|
|
108
179
|
{t(item.label)}
|
|
109
180
|
</TabsTrigger>
|
|
110
181
|
))}
|
|
111
182
|
</TabsList>
|
|
183
|
+
{data.report?.links?.length ? (
|
|
184
|
+
<nav className="shell-links" aria-label="Links">
|
|
185
|
+
{data.report.links.map((link, i) => (
|
|
186
|
+
<a key={i} href={link.href} target="_blank" rel="noreferrer">
|
|
187
|
+
{/* 内联 SVG 字标(可选)渲染在 label 前,原样内联;内容是作者义务,宿主不校验形状之外的东西。 */}
|
|
188
|
+
{link.icon ? (
|
|
189
|
+
<span className="shell-link-icon" aria-hidden="true" dangerouslySetInnerHTML={{ __html: link.icon.svg }} />
|
|
190
|
+
) : null}
|
|
191
|
+
{localizedText(link.label, locale) ?? link.href}
|
|
192
|
+
</a>
|
|
193
|
+
))}
|
|
194
|
+
</nav>
|
|
195
|
+
) : null}
|
|
112
196
|
<div className="lang-switch" aria-label="Language">
|
|
113
197
|
{(["en", "zh-CN"] satisfies Locale[]).map((item) => (
|
|
114
198
|
<button
|
|
@@ -125,7 +209,8 @@ export function App({ data, reportHtml }: { data: ViewData; reportHtml: ReportSl
|
|
|
125
209
|
</header>
|
|
126
210
|
<main>
|
|
127
211
|
<section className="hero">
|
|
128
|
-
|
|
212
|
+
{/* hero 标题 = 走完回退链的报告标题(与浏览器标题同源)。 */}
|
|
213
|
+
<h1>{shellTitle}</h1>
|
|
129
214
|
<div className="meta">
|
|
130
215
|
<span>
|
|
131
216
|
{/* viewData 只带原始值(ISO / number),这里按当前界面 locale 格式化。 */}
|
|
@@ -136,35 +221,46 @@ export function App({ data, reportHtml }: { data: ViewData; reportHtml: ReportSl
|
|
|
136
221
|
<span>{t("hero.composedFrom", { count: data.composedRuns })}</span>
|
|
137
222
|
) : null}
|
|
138
223
|
</div>
|
|
224
|
+
{/* 品牌行:恒在 hero 之下、恒带官网链接,不占 footer 的语义位、没有关闭配置
|
|
225
|
+
(shell.md「行为约束」)。 */}
|
|
226
|
+
<span className="powered-by">
|
|
227
|
+
<a href={NICEEVAL_SITE_URL} target="_blank" rel="noreferrer">
|
|
228
|
+
Powered by NiceEval
|
|
229
|
+
</a>
|
|
230
|
+
</span>
|
|
139
231
|
</section>
|
|
140
232
|
|
|
141
233
|
<SkippedRunsBanner skippedRuns={data.skippedRuns ?? []} t={t} />
|
|
142
234
|
|
|
143
|
-
|
|
144
|
-
{
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
<
|
|
148
|
-
|
|
149
|
-
<
|
|
235
|
+
{pages.map((page) => (
|
|
236
|
+
<TabsContent key={`page:${page.id}`} value={`page:${page.id}`} id={`tab-page-${page.id}`}>
|
|
237
|
+
{/* 壳区:报告槽上方靠右的批量修复 prompt 按钮。失败清单从 viewData.snapshots
|
|
238
|
+
现算(latest 口径),默认报告与 --report 两种填充下都在。 */}
|
|
239
|
+
<div className="section-sub-head">
|
|
240
|
+
<span className="group-detail-label" />
|
|
241
|
+
<div className="controls">
|
|
242
|
+
<CopyFixPrompt snapshots={snapshots} t={t} />
|
|
243
|
+
</div>
|
|
150
244
|
</div>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
dangerouslySetInnerHTML={{ __html: reportHtml[locale] || reportHtml.en }}
|
|
158
|
-
/>
|
|
159
|
-
</TabsContent>
|
|
245
|
+
{/* 报告槽:server 侧逐页渲染好的静态 HTML(含 <Style> 产物),按当前页与界面语言
|
|
246
|
+
摆放对应块;Scope 警告由报告页内呈现,壳不设第二条通道。
|
|
247
|
+
attempt 深链是普通 <a href="#/attempt/…">,经 hashchange 打开证据室弹窗。 */}
|
|
248
|
+
<ReportSlot html={reportPages[page.id]?.[locale] || reportPages[page.id]?.en || ""} />
|
|
249
|
+
</TabsContent>
|
|
250
|
+
))}
|
|
160
251
|
|
|
161
|
-
<TabsContent value="
|
|
162
|
-
<
|
|
252
|
+
<TabsContent value="attempts">
|
|
253
|
+
<AttemptsView attempts={attempts} t={t} />
|
|
163
254
|
</TabsContent>
|
|
164
255
|
<TabsContent value="traces">
|
|
165
256
|
<TracesView attempts={attempts} t={t} />
|
|
166
257
|
</TabsContent>
|
|
167
258
|
</main>
|
|
259
|
+
{footerText ? (
|
|
260
|
+
<footer className="site-footer">
|
|
261
|
+
<span className="site-footer-text">{footerText}</span>
|
|
262
|
+
</footer>
|
|
263
|
+
) : null}
|
|
168
264
|
{modalResult && <AttemptModal result={modalResult} onClose={closeModal} t={t} />}
|
|
169
265
|
</Tabs>
|
|
170
266
|
);
|