niceeval 0.5.4 → 0.6.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 +55 -0
- package/dist/agents/types.d.ts +317 -0
- package/dist/context/types.d.ts +335 -0
- package/dist/i18n/core.d.ts +11 -0
- package/dist/i18n/en.d.ts +181 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/zh-CN.d.ts +183 -0
- package/dist/o11y/execution-tree.d.ts +103 -0
- package/dist/o11y/otlp/parse.d.ts +3 -0
- package/dist/o11y/otlp/receiver.d.ts +21 -0
- package/dist/o11y/otlp/select.d.ts +22 -0
- package/dist/o11y/otlp/turn-otel.d.ts +43 -0
- package/dist/o11y/types.d.ts +190 -0
- package/dist/report/aggregate.d.ts +57 -0
- package/dist/report/aggregate.js +212 -0
- package/dist/report/built-ins/cost-pass-rate-comparison.d.ts +1 -0
- package/dist/report/built-ins/cost-pass-rate-comparison.js +17 -0
- package/dist/report/built-ins/index.d.ts +1 -0
- package/dist/report/built-ins/index.js +2 -0
- package/dist/report/components.d.ts +178 -0
- package/dist/report/components.js +132 -0
- package/dist/report/compute.d.ts +106 -0
- package/dist/report/compute.js +618 -0
- package/dist/report/flag.d.ts +6 -0
- package/dist/report/flag.js +8 -0
- package/dist/report/format.d.ts +32 -0
- package/dist/report/format.js +126 -0
- package/dist/report/index.d.ts +19 -0
- package/dist/report/index.js +30 -0
- package/dist/report/load.d.ts +12 -0
- package/dist/report/load.js +56 -0
- package/dist/report/locale.d.ts +83 -0
- package/dist/report/locale.js +160 -0
- package/dist/report/metrics.d.ts +21 -0
- package/dist/report/metrics.js +115 -0
- package/dist/report/primitives.d.ts +64 -0
- package/dist/report/primitives.js +113 -0
- package/dist/report/react/AttemptList.d.ts +23 -0
- package/dist/report/react/AttemptList.js +20 -0
- package/dist/report/react/DeltaTable.d.ts +8 -0
- package/dist/report/react/DeltaTable.js +24 -0
- package/dist/report/react/EvalList.d.ts +10 -0
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentList.d.ts +10 -0
- package/dist/report/react/ExperimentList.js +15 -0
- package/dist/report/react/GroupSummary.d.ts +8 -0
- package/dist/report/react/GroupSummary.js +8 -0
- package/dist/report/react/MetricBars.d.ts +10 -0
- package/dist/report/react/MetricBars.js +48 -0
- package/dist/report/react/MetricLine.d.ts +9 -0
- package/dist/report/react/MetricLine.js +68 -0
- package/dist/report/react/MetricMatrix.d.ts +10 -0
- package/dist/report/react/MetricMatrix.js +23 -0
- package/dist/report/react/MetricScatter.d.ts +9 -0
- package/dist/report/react/MetricScatter.js +100 -0
- package/dist/report/react/MetricTable.d.ts +11 -0
- package/dist/report/react/MetricTable.js +26 -0
- package/dist/report/react/RunOverview.d.ts +8 -0
- package/dist/report/react/RunOverview.js +12 -0
- package/dist/report/react/Scoreboard.d.ts +8 -0
- package/dist/report/react/Scoreboard.js +26 -0
- package/dist/report/react/cell.d.ts +9 -0
- package/dist/report/react/cell.js +9 -0
- package/dist/report/react/chart-math.d.ts +14 -0
- package/dist/report/react/chart-math.js +68 -0
- package/dist/report/react/colors.d.ts +14 -0
- package/dist/report/react/colors.js +45 -0
- package/dist/report/react/fixtures.d.ts +19 -0
- package/dist/report/react/fixtures.js +393 -0
- package/dist/report/react/format.d.ts +3 -0
- package/dist/report/react/format.js +7 -0
- package/dist/report/react/index.d.ts +6 -0
- package/dist/report/react/index.js +14 -0
- package/dist/report/report.d.ts +24 -0
- package/dist/report/report.js +49 -0
- package/dist/report/text/faces.d.ts +29 -0
- package/dist/report/text/faces.js +503 -0
- package/dist/report/text/layout.d.ts +75 -0
- package/dist/report/text/layout.js +173 -0
- package/dist/report/text/plot.d.ts +37 -0
- package/dist/report/text/plot.js +81 -0
- package/dist/report/text/table.d.ts +8 -0
- package/dist/report/text/table.js +86 -0
- package/dist/report/tree.d.ts +99 -0
- package/dist/report/tree.js +228 -0
- package/dist/report/types.d.ts +420 -0
- package/dist/report/types.js +4 -0
- package/dist/report/web.d.ts +15 -0
- package/dist/report/web.js +37 -0
- package/dist/results/annotated-source.d.ts +61 -0
- package/dist/results/attempt-evidence.d.ts +69 -0
- package/dist/results/attempt-source.d.ts +15 -0
- package/dist/results/locator.d.ts +75 -0
- package/dist/results/locator.js +0 -0
- package/dist/results/select.d.ts +45 -0
- package/dist/results/select.js +230 -0
- package/dist/results/source-hash.d.ts +9 -0
- package/dist/results/types.d.ts +204 -0
- package/dist/runner/eval-source.d.ts +22 -0
- package/dist/runner/fingerprint.d.ts +23 -0
- package/dist/runner/types.d.ts +332 -0
- package/dist/sandbox/docker-stream.d.ts +24 -0
- package/dist/sandbox/docker.d.ts +84 -0
- package/dist/sandbox/e2b.d.ts +31 -0
- package/dist/sandbox/errors.d.ts +4 -0
- package/dist/sandbox/local-files.d.ts +6 -0
- package/dist/sandbox/paths.d.ts +4 -0
- package/dist/sandbox/registry.d.ts +13 -0
- package/dist/sandbox/resolve.d.ts +40 -0
- package/dist/sandbox/retry.d.ts +10 -0
- package/dist/sandbox/shell.d.ts +11 -0
- package/dist/sandbox/source-files.d.ts +13 -0
- package/dist/sandbox/types.d.ts +203 -0
- package/dist/sandbox/vercel.d.ts +37 -0
- package/dist/scoring/types.d.ts +70 -0
- package/dist/shared/aggregate.d.ts +29 -0
- package/dist/shared/aggregate.js +55 -0
- package/dist/shared/types.d.ts +27 -0
- package/dist/shared/verdict.d.ts +27 -0
- package/dist/shared/verdict.js +34 -0
- package/dist/tty-line.d.ts +6 -0
- package/dist/types.d.ts +7 -0
- package/dist/util.d.ts +22 -0
- package/docs-site/zh/concepts/adapter.mdx +5 -5
- package/docs-site/zh/concepts/assert.mdx +8 -8
- package/docs-site/zh/concepts/evals.mdx +5 -5
- package/docs-site/zh/concepts/experiment.mdx +2 -2
- package/docs-site/zh/concepts/judge.mdx +9 -9
- package/docs-site/zh/concepts/overview.mdx +2 -2
- package/docs-site/zh/concepts/tier.mdx +6 -6
- package/docs-site/zh/example/claude-code-codex-plugin.mdx +9 -5
- package/docs-site/zh/example/claude-code-codex-skill.mdx +12 -7
- package/docs-site/zh/guides/agent-feedback-loop.mdx +235 -78
- package/docs-site/zh/guides/connect-otel.mdx +3 -3
- package/docs-site/zh/guides/connect-your-agent.mdx +2 -2
- package/docs-site/zh/guides/custom-reports.mdx +196 -36
- package/docs-site/zh/guides/experiments.mdx +7 -1
- package/docs-site/zh/guides/fixtures.mdx +1 -1
- package/docs-site/zh/guides/official-adapters.mdx +31 -9
- package/docs-site/zh/guides/publish-report.mdx +3 -3
- package/docs-site/zh/guides/report-components.mdx +194 -55
- package/docs-site/zh/guides/reporters.mdx +2 -2
- package/docs-site/zh/guides/results-data.mdx +35 -34
- package/docs-site/zh/guides/runner.mdx +2 -2
- package/docs-site/zh/guides/sandbox-agent.mdx +2 -2
- package/docs-site/zh/guides/{sandbox-backends.mdx → sandbox-providers.mdx} +11 -11
- package/docs-site/zh/guides/viewing-results.mdx +199 -79
- package/docs-site/zh/guides/write-experiment.mdx +9 -1
- package/docs-site/zh/guides/write-send.mdx +2 -2
- package/docs-site/zh/index.mdx +4 -3
- package/docs-site/zh/introduction.mdx +2 -1
- package/docs-site/zh/quickstart.mdx +5 -3
- package/docs-site/zh/reference/builtin-agents.mdx +38 -13
- package/docs-site/zh/reference/capabilities.mdx +1 -1
- package/docs-site/zh/reference/cli.mdx +36 -13
- package/docs-site/zh/reference/define-agent.mdx +12 -12
- package/docs-site/zh/reference/define-config.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +3 -3
- package/docs-site/zh/reference/events.mdx +7 -1
- package/docs-site/zh/reference/expect.mdx +3 -3
- package/package.json +10 -6
- package/src/agents/bub.ts +129 -42
- package/src/agents/claude-code.test.ts +152 -0
- package/src/agents/claude-code.ts +127 -13
- package/src/agents/codex.test.ts +192 -0
- package/src/agents/codex.ts +151 -12
- package/src/agents/index.ts +9 -3
- package/src/agents/manifest.ts +41 -0
- package/src/agents/plugin-config.test.ts +94 -0
- package/src/agents/shared.ts +33 -2
- package/src/agents/skills.test.ts +214 -0
- package/src/agents/skills.ts +304 -0
- package/src/agents/streaming.ts +5 -5
- package/src/agents/types.ts +54 -2
- package/src/cli.ts +74 -47
- package/src/context/context.test.ts +20 -2
- package/src/context/control-flow.ts +1 -1
- package/src/context/session.test.ts +92 -1
- package/src/context/session.ts +23 -1
- package/src/context/types.ts +4 -4
- package/src/define.test.ts +8 -8
- package/src/define.ts +11 -11
- package/src/expect/index.ts +5 -5
- package/src/i18n/en.ts +34 -15
- package/src/i18n/zh-CN.ts +37 -21
- package/src/index.ts +1 -1
- package/src/o11y/execution-tree.test.ts +452 -0
- package/src/o11y/execution-tree.ts +367 -0
- package/src/o11y/otlp/receiver.ts +1 -1
- package/src/o11y/otlp/select.ts +7 -3
- package/src/o11y/parsers/claude-code.test.ts +175 -0
- package/src/o11y/parsers/claude-code.ts +32 -0
- package/src/o11y/types.ts +6 -1
- package/src/report/aggregate.ts +26 -20
- package/src/report/built-in-user-parity.test.tsx +643 -0
- package/src/report/built-ins/cost-pass-rate-comparison.tsx +23 -0
- package/src/report/built-ins/index.ts +2 -0
- package/src/report/components.tsx +223 -50
- package/src/report/compute.ts +358 -53
- package/src/report/dual-render.test.tsx +1125 -0
- package/src/report/flag.ts +1 -1
- package/src/report/format.ts +50 -2
- package/src/report/index.ts +48 -14
- package/src/report/load.ts +1 -1
- package/src/report/locale.ts +206 -0
- package/src/report/metrics.ts +39 -15
- package/src/report/primitives.tsx +100 -2
- package/src/report/react/AttemptList.tsx +115 -0
- package/src/report/react/DeltaTable.tsx +9 -6
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentList.tsx +108 -0
- package/src/report/react/GroupSummary.tsx +66 -0
- package/src/report/react/MetricBars.tsx +24 -13
- package/src/report/react/MetricLine.tsx +21 -21
- package/src/report/react/MetricMatrix.tsx +8 -4
- package/src/report/react/MetricScatter.tsx +136 -96
- package/src/report/react/MetricTable.tsx +115 -15
- package/src/report/react/RunOverview.tsx +48 -26
- package/src/report/react/Scoreboard.tsx +15 -7
- package/src/report/react/cell.tsx +18 -9
- package/src/{view/app/lib/chart.ts → report/react/chart-math.ts} +2 -6
- package/src/report/react/colors.ts +24 -15
- package/src/report/react/enhance.js +129 -0
- package/src/report/react/fixtures.ts +163 -34
- package/src/report/react/format.ts +1 -1
- package/src/report/react/index.tsx +22 -6
- package/src/report/react/render.test.tsx +164 -31
- package/src/report/react/styles.css +405 -213
- package/src/report/report.test.ts +403 -94
- package/src/report/report.ts +29 -11
- package/src/report/text/faces.ts +303 -95
- package/src/report/text/layout.ts +76 -11
- package/src/report/text/table.ts +98 -0
- package/src/report/tree.ts +103 -20
- package/src/report/types.ts +196 -25
- package/src/report/web.ts +46 -19
- package/src/results/annotated-source.test.ts +146 -0
- package/src/results/annotated-source.ts +128 -0
- package/src/results/attempt-evidence.test.ts +158 -0
- package/src/results/attempt-evidence.ts +158 -0
- package/src/results/attempt-source.ts +48 -0
- package/src/results/copy.ts +154 -139
- package/src/results/format.ts +54 -47
- package/src/results/host-equivalence.test.ts +527 -0
- package/src/results/index.ts +56 -14
- package/src/results/locator.test.ts +198 -0
- package/src/results/locator.ts +0 -0
- package/src/results/open.ts +392 -175
- package/src/results/results.test.ts +943 -494
- package/src/results/select.ts +129 -23
- package/src/results/skipped-notice.ts +0 -0
- package/src/results/source-hash.ts +28 -0
- package/src/results/types.ts +98 -62
- package/src/results/writer.ts +304 -176
- package/src/runner/attempt.test.ts +170 -0
- package/src/runner/attempt.ts +48 -13
- package/src/runner/discover.test.ts +65 -0
- package/src/runner/discover.ts +6 -2
- package/src/runner/eval-source.test.ts +121 -0
- package/src/runner/eval-source.ts +45 -0
- package/src/runner/fingerprint.ts +54 -1
- package/src/runner/remote-sandbox.ts +1 -1
- package/src/runner/report.test.ts +6 -8
- package/src/runner/report.ts +5 -4
- package/src/runner/reporters/artifacts.ts +27 -41
- package/src/runner/reporters/braintrust.test.ts +3 -3
- package/src/runner/reporters/braintrust.ts +1 -1
- package/src/runner/reporters/console.ts +6 -6
- package/src/runner/reporters/json.ts +4 -4
- package/src/runner/reporters/live.test.ts +56 -0
- package/src/runner/reporters/live.ts +36 -10
- package/src/runner/reporters/quiet.test.ts +66 -0
- package/src/runner/reporters/quiet.ts +49 -0
- package/src/runner/reporters/shared.ts +5 -5
- package/src/runner/reporters/table.ts +17 -17
- package/src/runner/run.ts +61 -102
- package/src/runner/types.ts +43 -32
- package/src/sandbox/checkpoint.ts +1 -1
- package/src/sandbox/docker.ts +15 -2
- package/src/sandbox/e2b.ts +11 -5
- package/src/sandbox/errors.ts +13 -0
- package/src/sandbox/index.ts +3 -3
- package/src/sandbox/local-files.ts +1 -1
- package/src/sandbox/registry.ts +6 -5
- package/src/sandbox/resolve.ts +38 -23
- package/src/sandbox/retry.test.ts +68 -0
- package/src/sandbox/retry.ts +44 -0
- package/src/sandbox/types.ts +24 -24
- package/src/sandbox/vercel.ts +17 -3
- package/src/scoring/collector.ts +3 -3
- package/src/scoring/judge.ts +2 -2
- package/src/scoring/scoped.ts +13 -1
- package/src/scoring/types.ts +7 -7
- package/src/scoring/verdict.ts +4 -4
- package/src/shared/aggregate.ts +23 -3
- package/src/shared/types.ts +2 -2
- package/src/shared/{outcome.ts → verdict.ts} +15 -15
- package/src/show/compose.ts +12 -132
- package/src/show/index.ts +125 -94
- package/src/show/render.ts +660 -160
- package/src/show/show.test.ts +472 -253
- package/src/source-loc.ts +1 -1
- package/src/tty-line.ts +32 -0
- package/src/types.ts +2 -2
- package/src/util.test.ts +21 -1
- package/src/util.ts +5 -1
- package/src/view/app/App.tsx +33 -198
- package/src/view/app/components/AttemptModal.tsx +6 -6
- package/src/view/app/components/CodeView.tsx +2 -2
- package/src/view/app/components/CopyControls.tsx +32 -36
- package/src/view/app/components/SkippedRunsBanner.tsx +140 -0
- package/src/view/app/components/ui/badge.tsx +1 -1
- package/src/view/app/components/ui/dialog.tsx +2 -2
- package/src/view/app/i18n.ts +40 -31
- package/src/view/app/lib/artifact-url.ts +1 -1
- package/src/view/app/lib/attempt-route.test.ts +37 -47
- package/src/view/app/lib/attempt-route.ts +26 -26
- package/src/view/app/lib/rows.ts +9 -112
- package/src/view/app/lib/verdict.ts +25 -0
- package/src/view/app/main.tsx +9 -12
- package/src/view/app/pages/RunsPage.tsx +4 -4
- package/src/view/app/pages/TracesPage.tsx +3 -3
- package/src/view/app/shared.ts +0 -1
- package/src/view/app/types.ts +6 -51
- package/src/view/artifact-serving.test.ts +136 -0
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +21 -21
- package/src/view/data.test.ts +214 -143
- package/src/view/data.ts +166 -120
- package/src/view/index.ts +23 -11
- package/src/view/server.ts +52 -17
- package/src/view/shared/types.ts +22 -31
- package/src/view/styles.css +117 -327
- package/src/view/view-report.test.ts +146 -98
- package/src/report/default-report.tsx +0 -222
- package/src/report/dual-face.test.tsx +0 -527
- package/src/report/react/CaseList.tsx +0 -70
- package/src/view/app/components/CostScoreChart.tsx +0 -147
- package/src/view/app/components/ExperimentTable.tsx +0 -284
- package/src/view/app/components/GroupSelector.tsx +0 -61
- package/src/view/app/components/primitives.tsx +0 -43
- package/src/view/app/lib/outcome.ts +0 -82
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import type { T } from "../shared.ts";
|
|
3
|
-
import type { ViewRow } from "../types.ts";
|
|
4
|
-
import { CELL_KEYS } from "../lib/rows.ts";
|
|
5
|
-
import { formatCost, formatPercent } from "../lib/format.ts";
|
|
6
|
-
import { layoutLabelOffsets, niceTicks, seriesColor } from "../lib/chart.ts";
|
|
7
|
-
|
|
8
|
-
const WIDTH = 640;
|
|
9
|
-
const HEIGHT = 300;
|
|
10
|
-
const MARGIN = { top: 16, right: 24, bottom: 34, left: 46 };
|
|
11
|
-
|
|
12
|
-
/** formatCost(0) 显示 "—"(表示"没测出成本"),但坐标轴原点确实就是 $0,不能沿用那个语义。 */
|
|
13
|
-
function formatAxisCost(value: number): string {
|
|
14
|
-
return value === 0 ? "$0" : formatCost(value);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface ChartPoint {
|
|
18
|
-
key: string;
|
|
19
|
-
label: string;
|
|
20
|
-
agent: string;
|
|
21
|
-
model?: string;
|
|
22
|
-
/** 官方 cost 格子的值(平均每 eval 成本,与轴标签同口径)。 */
|
|
23
|
-
cost: number;
|
|
24
|
-
/** 官方 pass-rate 格子的值(0–1)。 */
|
|
25
|
-
rate: number;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* 一个 group 内「成本 vs 通过率」散点图:一行(一个实验/config)= 一个点。
|
|
30
|
-
* 点位取官方 MetricTable.data 的格子值;任一轴缺数据(null)的行不画。
|
|
31
|
-
* 只在 group 内至少两条 row 有成本时渲染,否则没有比较意义。
|
|
32
|
-
*/
|
|
33
|
-
export function CostScoreChart({ rows, t }: { rows: ViewRow[]; t: T }) {
|
|
34
|
-
const [hoverKey, setHoverKey] = useState<string | null>(null);
|
|
35
|
-
const points: ChartPoint[] = [];
|
|
36
|
-
for (const row of rows) {
|
|
37
|
-
const cost = row.cells[CELL_KEYS.cost]?.value ?? null;
|
|
38
|
-
const rate = row.cells[CELL_KEYS.passRate]?.value ?? null;
|
|
39
|
-
if (cost === null || rate === null || !Number.isFinite(cost)) continue;
|
|
40
|
-
points.push({ key: row.key, label: row.label, agent: row.agent, model: row.model, cost, rate });
|
|
41
|
-
}
|
|
42
|
-
if (points.length < 2) return null;
|
|
43
|
-
|
|
44
|
-
const plotW = WIDTH - MARGIN.left - MARGIN.right;
|
|
45
|
-
const plotH = HEIGHT - MARGIN.top - MARGIN.bottom;
|
|
46
|
-
|
|
47
|
-
const xTicks = niceTicks(0, Math.max(...points.map((p) => p.cost)), 5);
|
|
48
|
-
const xMax = xTicks[xTicks.length - 1] || 1;
|
|
49
|
-
|
|
50
|
-
const rates = points.map((p) => p.rate);
|
|
51
|
-
const rateMin = Math.min(...rates);
|
|
52
|
-
const rateMax = Math.max(...rates);
|
|
53
|
-
const pad = Math.max(0.03, (rateMax - rateMin) * 0.25);
|
|
54
|
-
const yTicksRaw = niceTicks(Math.max(0, rateMin - pad), Math.min(1, rateMax + pad), 5);
|
|
55
|
-
const yMin = Math.max(0, yTicksRaw[0] ?? 0);
|
|
56
|
-
const yMax = Math.min(1, yTicksRaw[yTicksRaw.length - 1] ?? 1);
|
|
57
|
-
const yTicks = yTicksRaw.filter((v) => v >= yMin - 1e-9 && v <= yMax + 1e-9);
|
|
58
|
-
|
|
59
|
-
// X 轴反向:贵在左、便宜在右,「又好又便宜」落在右上角,离原点越远越好。
|
|
60
|
-
const xScale = (cost: number) => MARGIN.left + (1 - cost / xMax) * plotW;
|
|
61
|
-
const yScale = (rate: number) => MARGIN.top + (1 - (rate - yMin) / (yMax - yMin)) * plotH;
|
|
62
|
-
|
|
63
|
-
const hovered = points.find((p) => p.key === hoverKey);
|
|
64
|
-
|
|
65
|
-
const positions = points.map((p) => {
|
|
66
|
-
const cx = xScale(p.cost);
|
|
67
|
-
const cy = yScale(p.rate);
|
|
68
|
-
const anchorLeft = cx >= MARGIN.left + plotW * 0.72;
|
|
69
|
-
return { cx, cy, anchorLeft, width: p.label.length * 6.4 + 10 };
|
|
70
|
-
});
|
|
71
|
-
const labelOffsets = layoutLabelOffsets(positions);
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<div className="cost-score-chart">
|
|
75
|
-
<div className="csc-head">{t("chart.costVsScore")}</div>
|
|
76
|
-
<div className="csc-svg-wrap">
|
|
77
|
-
<svg className="csc-svg" viewBox={`0 0 ${WIDTH} ${HEIGHT}`} role="img" aria-label={t("chart.costVsScore")}>
|
|
78
|
-
<g className="csc-grid">
|
|
79
|
-
{yTicks.map((tick) => (
|
|
80
|
-
<line key={`gy-${tick}`} x1={MARGIN.left} x2={WIDTH - MARGIN.right} y1={yScale(tick)} y2={yScale(tick)} />
|
|
81
|
-
))}
|
|
82
|
-
{xTicks.map((tick) => (
|
|
83
|
-
<line key={`gx-${tick}`} y1={MARGIN.top} y2={HEIGHT - MARGIN.bottom} x1={xScale(tick)} x2={xScale(tick)} />
|
|
84
|
-
))}
|
|
85
|
-
</g>
|
|
86
|
-
<g className="csc-axis csc-axis-y">
|
|
87
|
-
{yTicks.map((tick) => (
|
|
88
|
-
<text key={`ay-${tick}`} x={MARGIN.left - 8} y={yScale(tick)} textAnchor="end" dominantBaseline="middle">
|
|
89
|
-
{formatPercent(tick)}
|
|
90
|
-
</text>
|
|
91
|
-
))}
|
|
92
|
-
</g>
|
|
93
|
-
<g className="csc-axis csc-axis-x">
|
|
94
|
-
{xTicks.map((tick) => (
|
|
95
|
-
<text key={`ax-${tick}`} x={xScale(tick)} y={HEIGHT - MARGIN.bottom + 18} textAnchor="middle">
|
|
96
|
-
{formatAxisCost(tick)}
|
|
97
|
-
</text>
|
|
98
|
-
))}
|
|
99
|
-
<text x={MARGIN.left + plotW / 2} y={HEIGHT - 4} textAnchor="middle">
|
|
100
|
-
{t("chart.axisCost")}
|
|
101
|
-
</text>
|
|
102
|
-
</g>
|
|
103
|
-
<g className="csc-points">
|
|
104
|
-
{points.map((p, i) => {
|
|
105
|
-
const { cx, cy, anchorLeft } = positions[i]!;
|
|
106
|
-
const labelY = cy + 4 + labelOffsets[i]!;
|
|
107
|
-
const color = seriesColor(i);
|
|
108
|
-
const labelX = cx + (anchorLeft ? -10 : 10);
|
|
109
|
-
return (
|
|
110
|
-
<g
|
|
111
|
-
key={p.key}
|
|
112
|
-
className="csc-point"
|
|
113
|
-
tabIndex={0}
|
|
114
|
-
role="button"
|
|
115
|
-
aria-label={`${p.label}: ${formatCost(p.cost)}, ${formatPercent(p.rate)}`}
|
|
116
|
-
onMouseEnter={() => setHoverKey(p.key)}
|
|
117
|
-
onMouseLeave={() => setHoverKey((k) => (k === p.key ? null : k))}
|
|
118
|
-
onFocus={() => setHoverKey(p.key)}
|
|
119
|
-
onBlur={() => setHoverKey((k) => (k === p.key ? null : k))}
|
|
120
|
-
>
|
|
121
|
-
{/* 标签被挤开时补一条 leader line,避免脱离原点看不出对应关系。 */}
|
|
122
|
-
{labelOffsets[i] ? <line className="csc-leader" x1={cx} y1={cy} x2={labelX} y2={labelY - 4} /> : null}
|
|
123
|
-
<circle className="csc-hit" cx={cx} cy={cy} r={13} />
|
|
124
|
-
<circle className="csc-dot" cx={cx} cy={cy} r={5} style={{ fill: color }} />
|
|
125
|
-
<text x={labelX} y={labelY} textAnchor={anchorLeft ? "end" : "start"}>
|
|
126
|
-
{p.label}
|
|
127
|
-
</text>
|
|
128
|
-
</g>
|
|
129
|
-
);
|
|
130
|
-
})}
|
|
131
|
-
</g>
|
|
132
|
-
</svg>
|
|
133
|
-
{hovered ? (
|
|
134
|
-
<div
|
|
135
|
-
className="csc-tooltip"
|
|
136
|
-
style={{ left: `${(xScale(hovered.cost) / WIDTH) * 100}%`, top: `${(yScale(hovered.rate) / HEIGHT) * 100}%` }}
|
|
137
|
-
>
|
|
138
|
-
<b>{formatPercent(hovered.rate)}</b> {t("chart.axisScore")}
|
|
139
|
-
<div className="csc-tooltip-meta">
|
|
140
|
-
{formatCost(hovered.cost)} · {hovered.model || hovered.agent}
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
) : null}
|
|
144
|
-
</div>
|
|
145
|
-
</div>
|
|
146
|
-
);
|
|
147
|
-
}
|
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
import { ChevronRight } from "lucide-react";
|
|
3
|
-
import type { OpenModal, T } from "../shared.ts";
|
|
4
|
-
import type { Assertion, SortKey, SortState, ViewResult, ViewRow } from "../types.ts";
|
|
5
|
-
import { EvalGroup, failingAssertions, groupByEval, outcomeClass, outcomeLabel, outcomeSummary, reasonFor, scoresSummary } from "../lib/outcome.ts";
|
|
6
|
-
import { CELL_KEYS, configChips } from "../lib/rows.ts";
|
|
7
|
-
import { formatClock, formatCost, formatDateTime, formatDuration, formatTokens, totalTokens } from "../lib/format.ts";
|
|
8
|
-
import { Kpi, SortHeader } from "./primitives.tsx";
|
|
9
|
-
import type { MetricCell } from "../types.ts";
|
|
10
|
-
|
|
11
|
-
/** 官方格子的渲染:display 已格式化;samples < total 时 title 如实报覆盖率(有 attempt 测不了这个指标)。 */
|
|
12
|
-
function CellValue({ cell }: { cell: MetricCell | undefined }) {
|
|
13
|
-
if (!cell || cell.value === null) return <>—</>;
|
|
14
|
-
const partial = cell.samples < cell.total;
|
|
15
|
-
return <span title={partial ? `${cell.samples}/${cell.total} attempts measured` : undefined}>{cell.display}</span>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function ExperimentTable({
|
|
19
|
-
rows,
|
|
20
|
-
sort,
|
|
21
|
-
setSortKey,
|
|
22
|
-
openRows,
|
|
23
|
-
toggleRow,
|
|
24
|
-
openModal,
|
|
25
|
-
t,
|
|
26
|
-
}: {
|
|
27
|
-
rows: ViewRow[];
|
|
28
|
-
sort: SortState;
|
|
29
|
-
setSortKey: (key: SortKey) => void;
|
|
30
|
-
openRows: Set<string>;
|
|
31
|
-
toggleRow: (key: string) => void;
|
|
32
|
-
openModal: OpenModal;
|
|
33
|
-
t: T;
|
|
34
|
-
}) {
|
|
35
|
-
return (
|
|
36
|
-
<div className="table-wrap">
|
|
37
|
-
<table>
|
|
38
|
-
<thead>
|
|
39
|
-
<tr>
|
|
40
|
-
<SortHeader name={t("table.experiment")} sortKey="experiment" sort={sort} onSort={setSortKey} />
|
|
41
|
-
<SortHeader name={t("table.model")} sortKey="model" sort={sort} onSort={setSortKey} />
|
|
42
|
-
<SortHeader name={t("table.agent")} sortKey="agent" sort={sort} onSort={setSortKey} />
|
|
43
|
-
<SortHeader name={t("table.avgDuration")} sortKey="duration" sort={sort} onSort={setSortKey} />
|
|
44
|
-
<SortHeader name={t("table.successRate")} sortKey="passRate" sort={sort} onSort={setSortKey} />
|
|
45
|
-
<SortHeader name={t("table.tokens")} sortKey="tokens" sort={sort} onSort={setSortKey} />
|
|
46
|
-
<SortHeader name={t("table.estCost")} sortKey="cost" sort={sort} onSort={setSortKey} />
|
|
47
|
-
<th>{t("table.outcomes")}</th>
|
|
48
|
-
</tr>
|
|
49
|
-
</thead>
|
|
50
|
-
<tbody>
|
|
51
|
-
{rows.map((row: ViewRow) => (
|
|
52
|
-
<React.Fragment key={row.key}>
|
|
53
|
-
<ExperimentRow row={row} open={openRows.has(row.key)} onToggle={() => toggleRow(row.key)} t={t} />
|
|
54
|
-
{openRows.has(row.key) ? <ExperimentDetail row={row} openModal={openModal} t={t} /> : null}
|
|
55
|
-
</React.Fragment>
|
|
56
|
-
))}
|
|
57
|
-
</tbody>
|
|
58
|
-
</table>
|
|
59
|
-
</div>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function ExperimentRow({ row, open, onToggle, t }: { row: ViewRow; open: boolean; onToggle: () => void; t: T }) {
|
|
64
|
-
const passRate = row.cells[CELL_KEYS.passRate]?.value ?? null;
|
|
65
|
-
const tone = passRate === null ? "" : passRate >= 0.8 ? "good" : passRate >= 0.5 ? "warn" : "bad";
|
|
66
|
-
return (
|
|
67
|
-
<tr
|
|
68
|
-
className={`main-row${open ? " is-open" : ""}`}
|
|
69
|
-
tabIndex={0}
|
|
70
|
-
onClick={onToggle}
|
|
71
|
-
onKeyDown={(e) => {
|
|
72
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
73
|
-
e.preventDefault();
|
|
74
|
-
onToggle();
|
|
75
|
-
}
|
|
76
|
-
}}
|
|
77
|
-
>
|
|
78
|
-
<td>
|
|
79
|
-
<ChevronRight className="chev-icon" aria-hidden="true" />
|
|
80
|
-
<span className="name">{row.label}</span>
|
|
81
|
-
<div className="sub">
|
|
82
|
-
{row.evals} {row.evals === 1 ? t("detail.evalResult") : t("detail.evalResults")}
|
|
83
|
-
{row.runs > row.evals ? ` · ${row.runs} ${t("detail.runsUnit")}` : ""}
|
|
84
|
-
{row.lastRunAt ? ` · ${formatDateTime(row.lastRunAt)}` : ""}
|
|
85
|
-
</div>
|
|
86
|
-
</td>
|
|
87
|
-
<td>{row.model || t("config.default")}</td>
|
|
88
|
-
<td>{row.agent}</td>
|
|
89
|
-
{/* 官方 MetricTable.data 的格子:display 直接渲染,数字口径与 show 榜单同源。 */}
|
|
90
|
-
<td className="num">
|
|
91
|
-
<CellValue cell={row.cells[CELL_KEYS.duration]} />
|
|
92
|
-
</td>
|
|
93
|
-
<td className={`num ${tone}`}>
|
|
94
|
-
<CellValue cell={row.cells[CELL_KEYS.passRate]} />
|
|
95
|
-
</td>
|
|
96
|
-
<td className="num">
|
|
97
|
-
<CellValue cell={row.cells[CELL_KEYS.tokens]} />
|
|
98
|
-
</td>
|
|
99
|
-
<td className="num">
|
|
100
|
-
<CellValue cell={row.cells[CELL_KEYS.cost]} />
|
|
101
|
-
</td>
|
|
102
|
-
<td>
|
|
103
|
-
<span className="pill">{outcomeSummary(row, t)}</span>
|
|
104
|
-
</td>
|
|
105
|
-
</tr>
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function ExperimentDetail({ row, openModal, t }: { row: ViewRow; openModal: OpenModal; t: T }) {
|
|
110
|
-
const totalDuration = (row.results ?? []).reduce((sum: number, r: ViewResult) => sum + (r.durationMs || 0), 0);
|
|
111
|
-
const sampleResult =
|
|
112
|
-
row.results?.find((r: ViewResult) => r.outcome === "errored") ||
|
|
113
|
-
row.results?.find((r: ViewResult) => r.outcome === "failed") ||
|
|
114
|
-
row.results?.[0] ||
|
|
115
|
-
{};
|
|
116
|
-
const evalGroups = groupByEval(row.results ?? []).sort((a, b) => a.id.localeCompare(b.id));
|
|
117
|
-
return (
|
|
118
|
-
<tr className="detail-row">
|
|
119
|
-
<td className="detail-cell" colSpan={8}>
|
|
120
|
-
<div className="detail">
|
|
121
|
-
<div className="config-strip">
|
|
122
|
-
{configChips(row, t).map(([label, value]) => (
|
|
123
|
-
<span className="config-chip" key={label}>
|
|
124
|
-
<span>{label}</span>
|
|
125
|
-
<b>{value}</b>
|
|
126
|
-
</span>
|
|
127
|
-
))}
|
|
128
|
-
</div>
|
|
129
|
-
<div className="detail-kpis">
|
|
130
|
-
<Kpi label={t("detail.evals")} value={row.evals} />
|
|
131
|
-
<Kpi label={t("detail.passed")} value={row.passed} className="good" />
|
|
132
|
-
<Kpi label={t("detail.failed")} value={row.failed} className={row.failed ? "bad" : ""} />
|
|
133
|
-
<Kpi label={t("detail.errored")} value={row.errored} className={row.errored ? "infra-err" : ""} />
|
|
134
|
-
{row.runs > row.evals ? <Kpi label={t("detail.runs")} value={row.runs} /> : null}
|
|
135
|
-
<Kpi label={t("detail.totalTime")} value={formatDuration(totalDuration)} />
|
|
136
|
-
<Kpi label={t("detail.totalCost")} value={formatCost(row.totalCostUSD)} />
|
|
137
|
-
<Kpi label={t("detail.ran")} value={formatDateTime(row.lastRunAt)} title={row.lastRunAt || ""} />
|
|
138
|
-
</div>
|
|
139
|
-
<h3>{t("detail.evaluationAttempts")}</h3>
|
|
140
|
-
<div className="eval-list">
|
|
141
|
-
<div className="eval-grid-head">
|
|
142
|
-
<span>{t("detail.status")}</span>
|
|
143
|
-
<span>{t("detail.eval")}</span>
|
|
144
|
-
<span>{t("detail.reason")}</span>
|
|
145
|
-
<span>{t("detail.time")}</span>
|
|
146
|
-
<span>{t("table.tokens")}</span>
|
|
147
|
-
<span>{t("table.estCost")}</span>
|
|
148
|
-
<span>{t("detail.run")}</span>
|
|
149
|
-
</div>
|
|
150
|
-
{evalGroups.map((group) => (
|
|
151
|
-
<EvalRow key={`${group.experimentId ?? ""}-${group.id}`} group={group} openModal={openModal} t={t} />
|
|
152
|
-
))}
|
|
153
|
-
</div>
|
|
154
|
-
<details className="raw-details">
|
|
155
|
-
<summary>
|
|
156
|
-
{t("detail.rawSample")} <span className="raw-note">{t("detail.rawNote")}</span>
|
|
157
|
-
</summary>
|
|
158
|
-
<pre>{JSON.stringify(sampleResult, null, 2)}</pre>
|
|
159
|
-
</details>
|
|
160
|
-
</div>
|
|
161
|
-
</td>
|
|
162
|
-
</tr>
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* 一个 eval 一行:多轮(runs>1)折叠成单行摘要,点开展开各轮 attempt;单轮直接就是那条 attempt。
|
|
168
|
-
* Run 列显示 通过轮数 / 总轮数(如 0/3、2/3),让 earlyExit 重试和 flaky 一眼可见。
|
|
169
|
-
*/
|
|
170
|
-
export function EvalRow({ group, openModal, t }: { group: EvalGroup; openModal: OpenModal; t: T }) {
|
|
171
|
-
const [open, setOpen] = useState(false);
|
|
172
|
-
const n = group.attempts.length;
|
|
173
|
-
|
|
174
|
-
if (n === 1) {
|
|
175
|
-
return <Attempt result={group.attempts[0]!} totalRuns={1} openModal={openModal} t={t} />;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// 代表轮:取与 eval 判决相同的第一条,用它的原因/分数做折叠行摘要。
|
|
179
|
-
const rep = group.attempts.find((a) => a.outcome === group.outcome) ?? group.attempts[0]!;
|
|
180
|
-
const gates = failingAssertions(rep);
|
|
181
|
-
const reason = reasonFor(rep, gates) || (group.outcome === "passed" ? scoresSummary(rep.assertions || []) : "");
|
|
182
|
-
const totalDuration = group.attempts.reduce((s, a) => s + (a.durationMs || 0), 0);
|
|
183
|
-
const totalTok = group.attempts.reduce((s, a) => s + totalTokens(a.usage), 0);
|
|
184
|
-
const totalCost = group.attempts.reduce((s, a) => s + (a.estimatedCostUSD || 0), 0);
|
|
185
|
-
const toggle = () => setOpen((v) => !v);
|
|
186
|
-
|
|
187
|
-
return (
|
|
188
|
-
<>
|
|
189
|
-
<div
|
|
190
|
-
className={`eval-item eval-item-clickable eval-group${open ? " is-open" : ""}`}
|
|
191
|
-
role="button"
|
|
192
|
-
tabIndex={0}
|
|
193
|
-
aria-expanded={open}
|
|
194
|
-
onClick={toggle}
|
|
195
|
-
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); toggle(); } }}
|
|
196
|
-
>
|
|
197
|
-
<span className="attempt-status">
|
|
198
|
-
<span className={outcomeClass(group.outcome)}>{outcomeLabel(group.outcome, t)}</span>
|
|
199
|
-
</span>
|
|
200
|
-
<span className="eval-id">
|
|
201
|
-
<ChevronRight className="chev-icon" aria-hidden="true" />
|
|
202
|
-
{group.id}
|
|
203
|
-
</span>
|
|
204
|
-
<div className="assertions-cell">
|
|
205
|
-
<span className="assertions" title={reason || undefined}>
|
|
206
|
-
{reason || <span className="reason-empty">—</span>}
|
|
207
|
-
</span>
|
|
208
|
-
</div>
|
|
209
|
-
<span className="num">{formatDuration(totalDuration)}</span>
|
|
210
|
-
<span className="num">{formatTokens(totalTok)}</span>
|
|
211
|
-
<span className="num">{formatCost(totalCost)}</span>
|
|
212
|
-
<span className="num run-ratio" title={`${group.passedAttempts}/${n} ${t("detail.passed")}`}>
|
|
213
|
-
{group.passedAttempts}/{n}
|
|
214
|
-
</span>
|
|
215
|
-
</div>
|
|
216
|
-
{open ? (
|
|
217
|
-
<div className="eval-attempts">
|
|
218
|
-
{group.attempts.map((a) => (
|
|
219
|
-
<Attempt key={`${a.id}-${a.attempt}`} result={a} totalRuns={n} openModal={openModal} t={t} />
|
|
220
|
-
))}
|
|
221
|
-
</div>
|
|
222
|
-
) : null}
|
|
223
|
-
</>
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export function Attempt({ result, totalRuns, openModal, t }: { result: ViewResult; totalRuns: number; openModal: OpenModal; t: T }) {
|
|
228
|
-
const outcome = result.outcome;
|
|
229
|
-
const gates = failingAssertions(result);
|
|
230
|
-
const reason = reasonFor(result, gates);
|
|
231
|
-
const allAssertions = result.assertions || [];
|
|
232
|
-
const hasScores = allAssertions.some((a: Assertion) => a.score !== undefined && a.score !== null);
|
|
233
|
-
const hasBody = result.hasEvents || result.hasTrace || hasScores;
|
|
234
|
-
|
|
235
|
-
const inlineScores = !reason && outcome === "passed" ? scoresSummary(allAssertions) : "";
|
|
236
|
-
const displayReason = reason || inlineScores;
|
|
237
|
-
|
|
238
|
-
const handleOpen = () => openModal(result);
|
|
239
|
-
|
|
240
|
-
const cells = (
|
|
241
|
-
<>
|
|
242
|
-
<span className="attempt-status">
|
|
243
|
-
<span className={outcomeClass(outcome)}>{outcomeLabel(outcome, t)}</span>
|
|
244
|
-
</span>
|
|
245
|
-
<span className="eval-id">{result.id}</span>
|
|
246
|
-
<div className="assertions-cell">
|
|
247
|
-
<span
|
|
248
|
-
className={`assertions${hasBody ? " assertions-link" : ""}`}
|
|
249
|
-
title={displayReason || undefined}
|
|
250
|
-
onClick={hasBody ? (e) => { e.stopPropagation(); handleOpen(); } : undefined}
|
|
251
|
-
>
|
|
252
|
-
{displayReason || <span className="reason-empty">—</span>}
|
|
253
|
-
</span>
|
|
254
|
-
</div>
|
|
255
|
-
<span className="num">
|
|
256
|
-
{formatDuration(result.durationMs)}
|
|
257
|
-
{result.startedAt ? <small className="ran-at">{formatClock(result.startedAt)}</small> : null}
|
|
258
|
-
</span>
|
|
259
|
-
<span className="num">{formatTokens(totalTokens(result.usage))}</span>
|
|
260
|
-
<span className="num">{formatCost(result.estimatedCostUSD)}</span>
|
|
261
|
-
<span className="num" title={`attempt ${result.attempt + 1} of ${totalRuns}`}>
|
|
262
|
-
#{result.attempt + 1}
|
|
263
|
-
</span>
|
|
264
|
-
</>
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
if (!hasBody) {
|
|
268
|
-
return <div className="eval-item">{cells}</div>;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
return (
|
|
272
|
-
<div
|
|
273
|
-
className="eval-item eval-item-clickable"
|
|
274
|
-
role="button"
|
|
275
|
-
tabIndex={0}
|
|
276
|
-
onClick={handleOpen}
|
|
277
|
-
onKeyDown={(e) => {
|
|
278
|
-
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); handleOpen(); }
|
|
279
|
-
}}
|
|
280
|
-
>
|
|
281
|
-
{cells}
|
|
282
|
-
</div>
|
|
283
|
-
);
|
|
284
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import type { T } from "../shared.ts";
|
|
2
|
-
import type { ViewResult, ViewRow } from "../types.ts";
|
|
3
|
-
import { evalPassRate, groupByEval } from "../lib/outcome.ts";
|
|
4
|
-
import { formatCost, formatDateTime, formatPercent } from "../lib/format.ts";
|
|
5
|
-
|
|
6
|
-
export function GroupSelector({
|
|
7
|
-
groupMap,
|
|
8
|
-
selectedGroup,
|
|
9
|
-
onSelect,
|
|
10
|
-
t,
|
|
11
|
-
}: {
|
|
12
|
-
groupMap: Map<string, ViewRow[]>;
|
|
13
|
-
selectedGroup: string | null;
|
|
14
|
-
onSelect: (group: string) => void;
|
|
15
|
-
t: T;
|
|
16
|
-
}) {
|
|
17
|
-
if (!groupMap.size) return <div id="group-selector" className="group-selector" />;
|
|
18
|
-
return (
|
|
19
|
-
<div id="group-selector" className="group-selector">
|
|
20
|
-
{[...groupMap.keys()].sort().map((group) => {
|
|
21
|
-
const groupRows = groupMap.get(group) ?? [];
|
|
22
|
-
const allResults = groupRows.flatMap((r: ViewRow) => r.results ?? []);
|
|
23
|
-
const evalGroups = groupByEval(allResults); // 卡片计票按 eval,不按 attempt
|
|
24
|
-
const failed = evalGroups.filter((g) => g.outcome === "failed").length;
|
|
25
|
-
const errored = evalGroups.filter((g) => g.outcome === "errored").length;
|
|
26
|
-
const passRate = evalPassRate(allResults);
|
|
27
|
-
const tone = passRate >= 0.8 ? "good" : passRate >= 0.5 ? "warn" : "bad";
|
|
28
|
-
const totalCost = groupRows.reduce((s: number, r: ViewRow) => s + (r.totalCostUSD || 0), 0);
|
|
29
|
-
const lastRun = groupRows
|
|
30
|
-
.map((r: ViewRow) => r.lastRunAt)
|
|
31
|
-
.filter((value): value is string => Boolean(value))
|
|
32
|
-
.sort()
|
|
33
|
-
.at(-1);
|
|
34
|
-
const selected = selectedGroup === group;
|
|
35
|
-
return (
|
|
36
|
-
<div
|
|
37
|
-
key={group}
|
|
38
|
-
className={`group-card${selected ? " is-selected" : ""}`}
|
|
39
|
-
tabIndex={0}
|
|
40
|
-
role="button"
|
|
41
|
-
onClick={() => onSelect(group)}
|
|
42
|
-
onKeyDown={(e) => {
|
|
43
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
44
|
-
e.preventDefault();
|
|
45
|
-
onSelect(group);
|
|
46
|
-
}
|
|
47
|
-
}}
|
|
48
|
-
>
|
|
49
|
-
<div className="group-card-name">{group}</div>
|
|
50
|
-
<div className={`group-card-rate ${tone}`}>{formatPercent(passRate)}</div>
|
|
51
|
-
<div className="group-card-meta">
|
|
52
|
-
{groupRows.length} {groupRows.length === 1 ? t("detail.evalResult") : t("detail.evalResults")} · {failed} {t("outcome.failed")}
|
|
53
|
-
{errored ? ` · ${errored} ${t("outcome.errored")}` : ""} · {formatCost(totalCost)}
|
|
54
|
-
</div>
|
|
55
|
-
{lastRun ? <div className="group-card-time">{formatDateTime(lastRun)}</div> : null}
|
|
56
|
-
</div>
|
|
57
|
-
);
|
|
58
|
-
})}
|
|
59
|
-
</div>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
|
-
import type { SortKey, SortState } from "../types.ts";
|
|
3
|
-
|
|
4
|
-
export function Metric({ label, value }: { label: string; value: string }) {
|
|
5
|
-
return (
|
|
6
|
-
<div className="metric">
|
|
7
|
-
<div className="label">{label}</div>
|
|
8
|
-
<div className="value">{value}</div>
|
|
9
|
-
</div>
|
|
10
|
-
);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function SortHeader({
|
|
14
|
-
name,
|
|
15
|
-
sortKey,
|
|
16
|
-
sort,
|
|
17
|
-
onSort,
|
|
18
|
-
}: {
|
|
19
|
-
name: string;
|
|
20
|
-
sortKey: SortKey;
|
|
21
|
-
sort: SortState;
|
|
22
|
-
onSort: (key: SortKey) => void;
|
|
23
|
-
}) {
|
|
24
|
-
const sorted = sort.key === sortKey ? (sort.dir === 1 ? "asc" : "desc") : undefined;
|
|
25
|
-
return (
|
|
26
|
-
<th>
|
|
27
|
-
<button data-sorted={sorted} onClick={() => onSort(sortKey)}>
|
|
28
|
-
{name}
|
|
29
|
-
</button>
|
|
30
|
-
</th>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function Kpi({ label, value, className = "", title }: { label: string; value: ReactNode; className?: string; title?: string }) {
|
|
35
|
-
return (
|
|
36
|
-
<div className="detail-kpi">
|
|
37
|
-
<span>{label}</span>
|
|
38
|
-
<b className={className} title={title}>
|
|
39
|
-
{value}
|
|
40
|
-
</b>
|
|
41
|
-
</div>
|
|
42
|
-
);
|
|
43
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import type { Assertion, Outcome, ViewResult, ViewRow } from "../types.ts";
|
|
2
|
-
import type { T } from "../shared.ts";
|
|
3
|
-
import { formatScore } from "./format.ts";
|
|
4
|
-
|
|
5
|
-
// 折叠口径与 server 聚合共用一份实现,见 src/shared/outcome.ts。
|
|
6
|
-
import { foldEvalOutcome } from "../../../shared/outcome.ts";
|
|
7
|
-
|
|
8
|
-
export { foldEvalOutcome };
|
|
9
|
-
|
|
10
|
-
export interface EvalGroup {
|
|
11
|
-
id: string;
|
|
12
|
-
experimentId?: string;
|
|
13
|
-
outcome: Outcome;
|
|
14
|
-
attempts: ViewResult[];
|
|
15
|
-
passedAttempts: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** 把一批 attempt 按 (experimentId, eval id) 折叠成「每个 eval 一行」,内部 attempt 按轮次排序。 */
|
|
19
|
-
export function groupByEval(results: ViewResult[]): EvalGroup[] {
|
|
20
|
-
const byEval = new Map<string, ViewResult[]>();
|
|
21
|
-
for (const r of results) {
|
|
22
|
-
const key = `${r.experimentId ?? ""}|||${r.id}`;
|
|
23
|
-
byEval.set(key, [...(byEval.get(key) ?? []), r]);
|
|
24
|
-
}
|
|
25
|
-
return [...byEval.values()].map((attempts) => {
|
|
26
|
-
const sorted = [...attempts].sort((a, b) => a.attempt - b.attempt);
|
|
27
|
-
return {
|
|
28
|
-
id: sorted[0]!.id,
|
|
29
|
-
experimentId: sorted[0]!.experimentId,
|
|
30
|
-
outcome: foldEvalOutcome(sorted),
|
|
31
|
-
attempts: sorted,
|
|
32
|
-
passedAttempts: sorted.filter((a) => a.outcome === "passed").length,
|
|
33
|
-
};
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** 成功率按 eval 计票:折叠后通过的 eval 占已跑(非 skipped)eval 的比例。 */
|
|
38
|
-
export function evalPassRate(results: ViewResult[]): number {
|
|
39
|
-
const ran = groupByEval(results).filter((g) => g.outcome !== "skipped");
|
|
40
|
-
return ran.length ? ran.filter((g) => g.outcome === "passed").length / ran.length : 0;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function outcomeClass(outcome: Outcome): string {
|
|
44
|
-
return outcome === "passed" ? "good" : outcome === "errored" ? "infra-err" : outcome === "failed" ? "bad" : "warn";
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function outcomeLabel(outcome: Outcome, t: T): string {
|
|
48
|
-
if (outcome === "passed") return t("status.pass");
|
|
49
|
-
if (outcome === "failed") return t("status.fail");
|
|
50
|
-
if (outcome === "errored") return t("status.error");
|
|
51
|
-
if (outcome === "skipped") return t("status.skipped");
|
|
52
|
-
return outcome || "—";
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Only gate-severity failures are eval "failure reasons"; soft failures show as scores
|
|
56
|
-
export function failingAssertions(result: ViewResult): Assertion[] {
|
|
57
|
-
return (result.assertions || []).filter((a: Assertion) => !a.passed && a.severity === "gate");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function reasonFor(result: ViewResult, failedGates: Assertion[]): string {
|
|
61
|
-
if (result.error) return result.error;
|
|
62
|
-
if (result.skipReason) return result.skipReason;
|
|
63
|
-
return failedGates.map((a: Assertion) => (a.detail ? `${a.name}: ${a.detail}` : a.name)).join(", ");
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function scoresSummary(assertions: Assertion[]): string {
|
|
67
|
-
const scored = (assertions || []).filter((a: Assertion) => a.score !== undefined && a.score !== null);
|
|
68
|
-
if (!scored.length) return "";
|
|
69
|
-
return scored
|
|
70
|
-
.map((a: Assertion) => {
|
|
71
|
-
const s = formatScore(a.score);
|
|
72
|
-
return a.threshold !== undefined ? `${a.name} ${s}/${formatScore(a.threshold)}` : `${a.name} ${s}`;
|
|
73
|
-
})
|
|
74
|
-
.join(" · ");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function outcomeSummary(row: ViewRow, t: T): string {
|
|
78
|
-
const parts = [`${row.passed} ${t("outcome.passed")}`, `${row.failed} ${t("outcome.failed")}`];
|
|
79
|
-
if (row.errored) parts.push(`${row.errored} ${t("outcome.errored")}`);
|
|
80
|
-
if (row.skipped) parts.push(`${row.skipped} ${t("outcome.skipped")}`);
|
|
81
|
-
return parts.join(" / ");
|
|
82
|
-
}
|