niceeval 0.1.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/README.md +164 -0
- package/README.zh.md +160 -0
- package/bin/niceeval.js +11 -0
- package/package.json +96 -0
- package/src/agents/bub.ts +223 -0
- package/src/agents/builtin.ts +6 -0
- package/src/agents/claude-code.ts +96 -0
- package/src/agents/codex.ts +122 -0
- package/src/agents/index.ts +25 -0
- package/src/agents/shared.ts +145 -0
- package/src/cli.ts +421 -0
- package/src/context/context.test.ts +96 -0
- package/src/context/context.ts +427 -0
- package/src/context/control-flow.ts +27 -0
- package/src/context/session.ts +124 -0
- package/src/define.ts +112 -0
- package/src/expect/index.ts +243 -0
- package/src/i18n/en.ts +131 -0
- package/src/i18n/index.ts +39 -0
- package/src/i18n/zh-CN.ts +132 -0
- package/src/index.ts +39 -0
- package/src/loaders/index.ts +56 -0
- package/src/o11y/cost.ts +57 -0
- package/src/o11y/derive.ts +304 -0
- package/src/o11y/otlp/canonical.ts +112 -0
- package/src/o11y/otlp/mappers/bub.ts +30 -0
- package/src/o11y/otlp/mappers/codex.ts +38 -0
- package/src/o11y/otlp/mappers/index.ts +25 -0
- package/src/o11y/otlp/parse.ts +330 -0
- package/src/o11y/otlp/receiver.ts +100 -0
- package/src/o11y/otlp/sandbox-receiver.ts +113 -0
- package/src/o11y/otlp/select.ts +82 -0
- package/src/o11y/parsers/bub.ts +240 -0
- package/src/o11y/parsers/claude-code.ts +270 -0
- package/src/o11y/parsers/codex.ts +480 -0
- package/src/o11y/parsers/index.ts +37 -0
- package/src/o11y/prices.json +9873 -0
- package/src/runner/discover.ts +77 -0
- package/src/runner/reporters/artifacts.ts +81 -0
- package/src/runner/reporters/console.ts +76 -0
- package/src/runner/reporters/index.ts +5 -0
- package/src/runner/reporters/json.ts +52 -0
- package/src/runner/reporters/live.ts +178 -0
- package/src/runner/reporters/table.ts +328 -0
- package/src/runner/run.ts +860 -0
- package/src/runner/sandbox-prep.ts +91 -0
- package/src/sandbox/checkpoint.ts +39 -0
- package/src/sandbox/docker.ts +539 -0
- package/src/sandbox/e2b.ts +203 -0
- package/src/sandbox/index.ts +25 -0
- package/src/sandbox/registry.ts +60 -0
- package/src/sandbox/resolve.ts +131 -0
- package/src/sandbox/source-files.ts +30 -0
- package/src/sandbox/vercel.ts +236 -0
- package/src/scoring/collector.ts +113 -0
- package/src/scoring/judge.ts +236 -0
- package/src/scoring/scoped.ts +289 -0
- package/src/scoring/verdict.ts +20 -0
- package/src/source-loc.ts +53 -0
- package/src/types.ts +913 -0
- package/src/util.test.ts +31 -0
- package/src/util.ts +50 -0
- package/src/view/app/App.tsx +189 -0
- package/src/view/app/components/AttemptModal.tsx +66 -0
- package/src/view/app/components/CodeView.tsx +272 -0
- package/src/view/app/components/CopyControls.tsx +89 -0
- package/src/view/app/components/ExperimentTable.tsx +266 -0
- package/src/view/app/components/GroupSelector.tsx +61 -0
- package/src/view/app/components/LazyArtifact.tsx +50 -0
- package/src/view/app/components/Trace.tsx +100 -0
- package/src/view/app/components/Transcript.tsx +130 -0
- package/src/view/app/components/primitives.tsx +43 -0
- package/src/view/app/components/ui/badge.tsx +21 -0
- package/src/view/app/components/ui/dialog.tsx +34 -0
- package/src/view/app/components/ui/tabs.tsx +30 -0
- package/src/view/app/i18n.ts +341 -0
- package/src/view/app/index.html +13 -0
- package/src/view/app/lib/cn.ts +7 -0
- package/src/view/app/lib/format.ts +73 -0
- package/src/view/app/lib/guards.ts +61 -0
- package/src/view/app/lib/outcome.ts +96 -0
- package/src/view/app/lib/rows.ts +63 -0
- package/src/view/app/lib/transcript-data.tsx +121 -0
- package/src/view/app/main.tsx +17 -0
- package/src/view/app/pages/RunsPage.tsx +83 -0
- package/src/view/app/pages/TracesPage.tsx +40 -0
- package/src/view/app/shared.ts +10 -0
- package/src/view/app/types.ts +114 -0
- package/src/view/app/vite.config.ts +26 -0
- package/src/view/client-dist/app.css +2 -0
- package/src/view/client-dist/app.js +56 -0
- package/src/view/index.ts +406 -0
- package/src/view/styles.css +1074 -0
- package/src/view/template.html +15 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { T, ToolBlockCall } from "../shared.ts";
|
|
2
|
+
import type { ToolResultEvent, TranscriptEvent } from "../types.ts";
|
|
3
|
+
import { TOOL_VERB, resultBody, toolPrimaryArg } from "../lib/transcript-data.tsx";
|
|
4
|
+
import { prettyJson, previewText, truncate } from "../lib/format.ts";
|
|
5
|
+
|
|
6
|
+
export function Transcript({ events, t }: { events: TranscriptEvent[]; t: T }) {
|
|
7
|
+
if (!Array.isArray(events) || !events.length) return <div className="trace-span-meta">{t("transcript.noEvents")}</div>;
|
|
8
|
+
const resultByCall = new Map<string, ToolResultEvent>();
|
|
9
|
+
for (const event of events) {
|
|
10
|
+
if (event.type === "action.result" || event.type === "subagent.completed") resultByCall.set(event.callId, event);
|
|
11
|
+
}
|
|
12
|
+
const pairedResult = new Set<string>();
|
|
13
|
+
return (
|
|
14
|
+
<div className="transcript">
|
|
15
|
+
{events.map((event, index) => {
|
|
16
|
+
switch (event.type) {
|
|
17
|
+
case "message":
|
|
18
|
+
return <MessageBlock event={event} t={t} key={index} />;
|
|
19
|
+
case "thinking":
|
|
20
|
+
return <ThinkBlock event={event} t={t} key={index} />;
|
|
21
|
+
case "action.called": {
|
|
22
|
+
const result = resultByCall.get(event.callId);
|
|
23
|
+
if (result) pairedResult.add(event.callId);
|
|
24
|
+
return <ToolBlock call={event} result={result} t={t} key={index} />;
|
|
25
|
+
}
|
|
26
|
+
case "subagent.called": {
|
|
27
|
+
const result = resultByCall.get(event.callId);
|
|
28
|
+
if (result) pairedResult.add(event.callId);
|
|
29
|
+
return (
|
|
30
|
+
<ToolBlock
|
|
31
|
+
call={{ tool: "agent_task", name: event.name, input: { description: event.name, ...(event.remoteUrl ? { remoteUrl: event.remoteUrl } : {}) } }}
|
|
32
|
+
result={result}
|
|
33
|
+
t={t}
|
|
34
|
+
key={index}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
case "action.result":
|
|
39
|
+
case "subagent.completed":
|
|
40
|
+
return pairedResult.has(event.callId) ? null : (
|
|
41
|
+
<ToolBlock call={{ tool: "unknown", name: "result", input: null }} result={event} t={t} key={index} />
|
|
42
|
+
);
|
|
43
|
+
case "input.requested":
|
|
44
|
+
return <InputBlock event={event} t={t} key={index} />;
|
|
45
|
+
case "compaction":
|
|
46
|
+
return (
|
|
47
|
+
<div className="ts-compaction" key={index}>
|
|
48
|
+
{t("transcript.contextCompacted")}{event.reason ? " · " + event.reason : ""}
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
case "error":
|
|
52
|
+
return (
|
|
53
|
+
<div className="ts-error" key={index}>
|
|
54
|
+
! {event.message || "error"}
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
default:
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
})}
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function MessageBlock({ event, t }: { event: Extract<TranscriptEvent, { type: "message" }>; t: T }) {
|
|
66
|
+
const who = event.role === "assistant" ? "assistant" : "user";
|
|
67
|
+
return (
|
|
68
|
+
<div className={`ts-msg ts-${who}`}>
|
|
69
|
+
<span className="ts-role">{who === "assistant" ? t("transcript.assistant") : t("transcript.user")}</span>
|
|
70
|
+
<div className="ts-text">{event.text || ""}</div>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function ThinkBlock({ event, t }: { event: Extract<TranscriptEvent, { type: "thinking" }>; t: T }) {
|
|
76
|
+
return (
|
|
77
|
+
<details className="ts-think">
|
|
78
|
+
<summary>{t("transcript.thinking")}</summary>
|
|
79
|
+
<div className="ts-think-text">{event.text || ""}</div>
|
|
80
|
+
</details>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function InputBlock({ event, t }: { event: Extract<TranscriptEvent, { type: "input.requested" }>; t: T }) {
|
|
85
|
+
const request = event.request || {};
|
|
86
|
+
const opts = (request.options || []).map((o: { id: string; label?: string }) => o.label || o.id).filter(Boolean).join(" / ");
|
|
87
|
+
const body = (request.prompt || t("transcript.awaitingInput")) + (opts ? "\n[ " + opts + " ]" : "");
|
|
88
|
+
return (
|
|
89
|
+
<div className="ts-msg ts-input">
|
|
90
|
+
<span className="ts-role">{t("transcript.inputRequested")}</span>
|
|
91
|
+
<div className="ts-text">{body}</div>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function ToolBlock({ call, result, t }: { call: ToolBlockCall; result?: ToolResultEvent; t: T }) {
|
|
97
|
+
const verb = (call.tool ? TOOL_VERB[call.tool] : undefined) || call.name || call.tool || "tool";
|
|
98
|
+
const arg = toolPrimaryArg(call);
|
|
99
|
+
const label = arg ? `${verb}(${arg})` : verb;
|
|
100
|
+
const status = result ? result.status : "pending";
|
|
101
|
+
const dot = status === "failed" ? "bad" : status === "rejected" ? "warn" : status === "pending" ? "pending" : "good";
|
|
102
|
+
const inputStr = call.input == null ? "" : prettyJson(call.input);
|
|
103
|
+
const outBody = result ? resultBody(result.output) : "";
|
|
104
|
+
const preview = result ? previewText(outBody) : t("transcript.running");
|
|
105
|
+
return (
|
|
106
|
+
<details className="ts-tool-d">
|
|
107
|
+
<summary className="ts-row">
|
|
108
|
+
<span className={`ts-dot ${dot}`} />
|
|
109
|
+
<span className="ts-tool" title={label}>
|
|
110
|
+
{label}
|
|
111
|
+
</span>
|
|
112
|
+
<span className="ts-preview">{truncate(preview, 140)}</span>
|
|
113
|
+
</summary>
|
|
114
|
+
<div className="ts-body">
|
|
115
|
+
{inputStr ? (
|
|
116
|
+
<div className="ts-field">
|
|
117
|
+
<span className="ts-k">{t("transcript.input")}</span>
|
|
118
|
+
<pre className="attr-pre">{truncate(inputStr, 4000)}</pre>
|
|
119
|
+
</div>
|
|
120
|
+
) : null}
|
|
121
|
+
{result ? (
|
|
122
|
+
<div className="ts-field">
|
|
123
|
+
<span className="ts-k">{t("transcript.output")}{result.status && result.status !== "completed" ? " · " + result.status : ""}</span>
|
|
124
|
+
<pre className="attr-pre">{outBody ? truncate(outBody, 8000) : <span className="reason-empty">{t("transcript.empty")}</span>}</pre>
|
|
125
|
+
</div>
|
|
126
|
+
) : null}
|
|
127
|
+
</div>
|
|
128
|
+
</details>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { cn } from "../../lib/cn.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 状态小标签:边框 + 浅底都取自当前文字色,tone(.good/.bad/.warn/.infra-err)决定颜色。
|
|
6
|
+
* 取代旧的 .modal-outcome / 内联状态 chip。
|
|
7
|
+
*/
|
|
8
|
+
export function Badge({ tone, className, children }: { tone?: string; className?: string; children: ReactNode }) {
|
|
9
|
+
return (
|
|
10
|
+
<span
|
|
11
|
+
className={cn(
|
|
12
|
+
"inline-block w-fit rounded-[5px] border border-current bg-current/10 px-[7px] py-px",
|
|
13
|
+
"text-[10px] font-bold uppercase tracking-[0.05em]",
|
|
14
|
+
tone,
|
|
15
|
+
className,
|
|
16
|
+
)}
|
|
17
|
+
>
|
|
18
|
+
{children}
|
|
19
|
+
</span>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
2
|
+
import type { ComponentPropsWithoutRef } from "react";
|
|
3
|
+
import { cn } from "../../lib/cn.ts";
|
|
4
|
+
|
|
5
|
+
export const Dialog = DialogPrimitive.Root;
|
|
6
|
+
export const DialogClose = DialogPrimitive.Close;
|
|
7
|
+
export const DialogTitle = DialogPrimitive.Title;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Radix Dialog 内容容器:接管 portal / 焦点陷阱 / Esc 关闭 / 背景滚动锁 / 点遮罩关闭 ——
|
|
11
|
+
* 这些原本在 AttemptModal 里手搓(keydown + documentElement.overflow + stopPropagation),现在交给 Radix。
|
|
12
|
+
*/
|
|
13
|
+
export function DialogContent({
|
|
14
|
+
className,
|
|
15
|
+
children,
|
|
16
|
+
...props
|
|
17
|
+
}: ComponentPropsWithoutRef<typeof DialogPrimitive.Content>) {
|
|
18
|
+
return (
|
|
19
|
+
<DialogPrimitive.Portal>
|
|
20
|
+
<DialogPrimitive.Overlay className="fixed inset-0 z-[200] bg-black/50 backdrop-blur-[3px]" />
|
|
21
|
+
<DialogPrimitive.Content
|
|
22
|
+
className={cn(
|
|
23
|
+
"fixed left-1/2 top-1/2 z-[200] flex max-h-[min(86vh,820px)] w-[min(960px,calc(100vw-48px))]",
|
|
24
|
+
"-translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-xl border border-line-strong",
|
|
25
|
+
"bg-panel shadow-[0_20px_60px_rgba(0,0,0,0.45)] focus:outline-none",
|
|
26
|
+
className,
|
|
27
|
+
)}
|
|
28
|
+
{...props}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</DialogPrimitive.Content>
|
|
32
|
+
</DialogPrimitive.Portal>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
2
|
+
import type { ComponentPropsWithoutRef } from "react";
|
|
3
|
+
import { cn } from "../../lib/cn.ts";
|
|
4
|
+
|
|
5
|
+
export const Tabs = TabsPrimitive.Root;
|
|
6
|
+
export const TabsContent = TabsPrimitive.Content;
|
|
7
|
+
|
|
8
|
+
/** 顶部导航,改用 Radix Tabs(键盘左右切换 + role=tablist/aria-selected);样式 = 原 .nav。 */
|
|
9
|
+
export function TabsList({ className, ...props }: ComponentPropsWithoutRef<typeof TabsPrimitive.List>) {
|
|
10
|
+
return (
|
|
11
|
+
<TabsPrimitive.List
|
|
12
|
+
className={cn("flex items-center gap-[22px] text-sm text-muted max-[760px]:hidden", className)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** 单个 tab 触发器;样式 = 原 .nav-tab,active 态走 data-state 而非 .is-active。 */
|
|
19
|
+
export function TabsTrigger({ className, ...props }: ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>) {
|
|
20
|
+
return (
|
|
21
|
+
<TabsPrimitive.Trigger
|
|
22
|
+
className={cn(
|
|
23
|
+
"cursor-pointer border-none bg-transparent p-0 font-[inherit] text-sm text-muted transition-colors",
|
|
24
|
+
"hover:text-text data-[state=active]:font-[560] data-[state=active]:text-text",
|
|
25
|
+
className,
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import type { Locale } from "./types.ts";
|
|
2
|
+
|
|
3
|
+
export type MessageKey =
|
|
4
|
+
| "app.title"
|
|
5
|
+
| "nav.label"
|
|
6
|
+
| "nav.experiments"
|
|
7
|
+
| "nav.runs"
|
|
8
|
+
| "nav.traces"
|
|
9
|
+
| "hero.title"
|
|
10
|
+
| "hero.lastRun"
|
|
11
|
+
| "metric.passRate"
|
|
12
|
+
| "metric.evalResults"
|
|
13
|
+
| "metric.duration"
|
|
14
|
+
| "metric.cost"
|
|
15
|
+
| "section.experiments"
|
|
16
|
+
| "section.individualRuns"
|
|
17
|
+
| "section.traces"
|
|
18
|
+
| "search.experiments"
|
|
19
|
+
| "search.runs"
|
|
20
|
+
| "empty.summary"
|
|
21
|
+
| "empty.individualRuns"
|
|
22
|
+
| "empty.runsFilter"
|
|
23
|
+
| "empty.traces"
|
|
24
|
+
| "table.experiment"
|
|
25
|
+
| "table.model"
|
|
26
|
+
| "table.agent"
|
|
27
|
+
| "table.avgDuration"
|
|
28
|
+
| "table.successRate"
|
|
29
|
+
| "table.tokens"
|
|
30
|
+
| "table.estCost"
|
|
31
|
+
| "table.outcomes"
|
|
32
|
+
| "table.evalId"
|
|
33
|
+
| "table.outcome"
|
|
34
|
+
| "table.ranAt"
|
|
35
|
+
| "detail.evalResult"
|
|
36
|
+
| "detail.evalResults"
|
|
37
|
+
| "detail.attempts"
|
|
38
|
+
| "detail.evals"
|
|
39
|
+
| "detail.runs"
|
|
40
|
+
| "detail.runsUnit"
|
|
41
|
+
| "detail.passed"
|
|
42
|
+
| "detail.failed"
|
|
43
|
+
| "detail.errored"
|
|
44
|
+
| "detail.totalTime"
|
|
45
|
+
| "detail.totalCost"
|
|
46
|
+
| "detail.ran"
|
|
47
|
+
| "detail.evaluationAttempts"
|
|
48
|
+
| "detail.status"
|
|
49
|
+
| "detail.eval"
|
|
50
|
+
| "detail.reason"
|
|
51
|
+
| "detail.time"
|
|
52
|
+
| "detail.run"
|
|
53
|
+
| "detail.rawSample"
|
|
54
|
+
| "detail.rawNote"
|
|
55
|
+
| "config.experiment"
|
|
56
|
+
| "config.flagsNone"
|
|
57
|
+
| "config.default"
|
|
58
|
+
| "config.none"
|
|
59
|
+
| "config.notApplicable"
|
|
60
|
+
| "status.pass"
|
|
61
|
+
| "status.fail"
|
|
62
|
+
| "status.error"
|
|
63
|
+
| "status.skipped"
|
|
64
|
+
| "action.close"
|
|
65
|
+
| "action.copyReason"
|
|
66
|
+
| "action.copyErrors"
|
|
67
|
+
| "action.copied"
|
|
68
|
+
| "trace.loading"
|
|
69
|
+
| "trace.loadFailed"
|
|
70
|
+
| "trace.transcript"
|
|
71
|
+
| "trace.timing"
|
|
72
|
+
| "trace.noSpans"
|
|
73
|
+
| "trace.total"
|
|
74
|
+
| "trace.spans"
|
|
75
|
+
| "trace.clickDetails"
|
|
76
|
+
| "transcript.noEvents"
|
|
77
|
+
| "transcript.user"
|
|
78
|
+
| "transcript.assistant"
|
|
79
|
+
| "transcript.thinking"
|
|
80
|
+
| "transcript.inputRequested"
|
|
81
|
+
| "transcript.awaitingInput"
|
|
82
|
+
| "transcript.contextCompacted"
|
|
83
|
+
| "transcript.running"
|
|
84
|
+
| "transcript.input"
|
|
85
|
+
| "transcript.output"
|
|
86
|
+
| "transcript.empty"
|
|
87
|
+
| "code.otherAssertions"
|
|
88
|
+
| "code.noReply"
|
|
89
|
+
| "code.reply"
|
|
90
|
+
| "code.hide"
|
|
91
|
+
| "code.checks"
|
|
92
|
+
| "code.conversation"
|
|
93
|
+
| "code.noSource"
|
|
94
|
+
| "assert.pass"
|
|
95
|
+
| "assert.fail"
|
|
96
|
+
| "assert.soft"
|
|
97
|
+
| "assert.evidence"
|
|
98
|
+
| "outcome.passed"
|
|
99
|
+
| "outcome.failed"
|
|
100
|
+
| "outcome.errored"
|
|
101
|
+
| "outcome.skipped";
|
|
102
|
+
|
|
103
|
+
type Dictionary = Record<MessageKey, string>;
|
|
104
|
+
|
|
105
|
+
const dictionaries: Record<Locale, Dictionary> = {
|
|
106
|
+
en: {
|
|
107
|
+
"app.title": "niceeval experiment view",
|
|
108
|
+
"nav.label": "Report",
|
|
109
|
+
"nav.experiments": "Experiments",
|
|
110
|
+
"nav.runs": "Runs",
|
|
111
|
+
"nav.traces": "Traces",
|
|
112
|
+
"hero.title": "Eval Run Results",
|
|
113
|
+
"hero.lastRun": "Last run:",
|
|
114
|
+
"metric.passRate": "Pass Rate",
|
|
115
|
+
"metric.evalResults": "Eval Results",
|
|
116
|
+
"metric.duration": "Duration",
|
|
117
|
+
"metric.cost": "Estimated Cost",
|
|
118
|
+
"section.experiments": "Experiments",
|
|
119
|
+
"section.individualRuns": "Individual Runs",
|
|
120
|
+
"section.traces": "Traces",
|
|
121
|
+
"search.experiments": "Filter experiment, agent, model, or eval...",
|
|
122
|
+
"search.runs": "Filter eval ID or experiment...",
|
|
123
|
+
"empty.summary": "No summary.json files found. Run niceeval or pass niceeval view path/to/summary.json.",
|
|
124
|
+
"empty.individualRuns": "No individual runs found.",
|
|
125
|
+
"empty.runsFilter": "No results match the filter.",
|
|
126
|
+
"empty.traces": "No traces available. Traces are collected during eval runs when artifacts are saved.",
|
|
127
|
+
"table.experiment": "Experiment",
|
|
128
|
+
"table.model": "Model",
|
|
129
|
+
"table.agent": "Agent",
|
|
130
|
+
"table.avgDuration": "Avg Duration",
|
|
131
|
+
"table.successRate": "Success Rate",
|
|
132
|
+
"table.tokens": "Tokens",
|
|
133
|
+
"table.estCost": "Est. Cost",
|
|
134
|
+
"table.outcomes": "Outcomes",
|
|
135
|
+
"table.evalId": "Eval ID",
|
|
136
|
+
"table.outcome": "Outcome",
|
|
137
|
+
"table.ranAt": "Ran At",
|
|
138
|
+
"detail.evalResult": "eval",
|
|
139
|
+
"detail.evalResults": "evals",
|
|
140
|
+
"detail.attempts": "Attempts",
|
|
141
|
+
"detail.evals": "Evals",
|
|
142
|
+
"detail.runs": "Runs",
|
|
143
|
+
"detail.runsUnit": "runs",
|
|
144
|
+
"detail.passed": "Passed",
|
|
145
|
+
"detail.failed": "Failed",
|
|
146
|
+
"detail.errored": "Errored",
|
|
147
|
+
"detail.totalTime": "Total Time",
|
|
148
|
+
"detail.totalCost": "Total Cost",
|
|
149
|
+
"detail.ran": "Ran",
|
|
150
|
+
"detail.evaluationAttempts": "Evals",
|
|
151
|
+
"detail.status": "Status",
|
|
152
|
+
"detail.eval": "Eval",
|
|
153
|
+
"detail.reason": "Reason",
|
|
154
|
+
"detail.time": "Time",
|
|
155
|
+
"detail.run": "Run",
|
|
156
|
+
"detail.rawSample": "Raw sample result",
|
|
157
|
+
"detail.rawNote": "debug JSON, defaults to first error/failure when available",
|
|
158
|
+
"config.experiment": "experiment",
|
|
159
|
+
"config.flagsNone": "none",
|
|
160
|
+
"config.default": "default",
|
|
161
|
+
"config.none": "none",
|
|
162
|
+
"config.notApplicable": "n/a",
|
|
163
|
+
"status.pass": "pass",
|
|
164
|
+
"status.fail": "fail",
|
|
165
|
+
"status.error": "error",
|
|
166
|
+
"status.skipped": "skipped",
|
|
167
|
+
"action.close": "Close",
|
|
168
|
+
"action.copyReason": "Copy reason",
|
|
169
|
+
"action.copyErrors": "Copy errors",
|
|
170
|
+
"action.copied": "Copied",
|
|
171
|
+
"trace.loading": "loading...",
|
|
172
|
+
"trace.loadFailed": "load failed (static report has no server - use niceeval view):",
|
|
173
|
+
"trace.transcript": "transcript",
|
|
174
|
+
"trace.timing": "timing trace",
|
|
175
|
+
"trace.noSpans": "no spans",
|
|
176
|
+
"trace.total": "total",
|
|
177
|
+
"trace.spans": "spans",
|
|
178
|
+
"trace.clickDetails": "click a row for details",
|
|
179
|
+
"transcript.noEvents": "no events",
|
|
180
|
+
"transcript.user": "user",
|
|
181
|
+
"transcript.assistant": "assistant",
|
|
182
|
+
"transcript.thinking": "thinking",
|
|
183
|
+
"transcript.inputRequested": "input requested",
|
|
184
|
+
"transcript.awaitingInput": "(awaiting input)",
|
|
185
|
+
"transcript.contextCompacted": "context compacted",
|
|
186
|
+
"transcript.running": "running...",
|
|
187
|
+
"transcript.input": "input",
|
|
188
|
+
"transcript.output": "output",
|
|
189
|
+
"transcript.empty": "(empty)",
|
|
190
|
+
"code.otherAssertions": "other assertions",
|
|
191
|
+
"code.noReply": "(no reply)",
|
|
192
|
+
"code.reply": "reply",
|
|
193
|
+
"code.hide": "hide",
|
|
194
|
+
"code.checks": "checks",
|
|
195
|
+
"code.conversation": "conversation",
|
|
196
|
+
"code.noSource": "Source was not captured. This run may predate source-loc or the source may be unavailable. Re-run this eval to see the code view.",
|
|
197
|
+
"assert.pass": "pass",
|
|
198
|
+
"assert.fail": "fail",
|
|
199
|
+
"assert.soft": "soft",
|
|
200
|
+
"assert.evidence": "What the judge saw",
|
|
201
|
+
"outcome.passed": "passed",
|
|
202
|
+
"outcome.failed": "failed",
|
|
203
|
+
"outcome.errored": "errors",
|
|
204
|
+
"outcome.skipped": "skipped",
|
|
205
|
+
},
|
|
206
|
+
"zh-CN": {
|
|
207
|
+
"app.title": "niceeval 实验查看器",
|
|
208
|
+
"nav.label": "报告",
|
|
209
|
+
"nav.experiments": "实验",
|
|
210
|
+
"nav.runs": "运行",
|
|
211
|
+
"nav.traces": "追踪",
|
|
212
|
+
"hero.title": "Eval 运行结果",
|
|
213
|
+
"hero.lastRun": "最近运行:",
|
|
214
|
+
"metric.passRate": "通过率",
|
|
215
|
+
"metric.evalResults": "Eval 结果",
|
|
216
|
+
"metric.duration": "耗时",
|
|
217
|
+
"metric.cost": "预估成本",
|
|
218
|
+
"section.experiments": "实验",
|
|
219
|
+
"section.individualRuns": "单次运行",
|
|
220
|
+
"section.traces": "追踪",
|
|
221
|
+
"search.experiments": "筛选实验、agent、model 或 eval...",
|
|
222
|
+
"search.runs": "筛选 eval ID 或实验...",
|
|
223
|
+
"empty.summary": "没有找到 summary.json。请先运行 niceeval,或传入 niceeval view path/to/summary.json。",
|
|
224
|
+
"empty.individualRuns": "没有单次运行结果。",
|
|
225
|
+
"empty.runsFilter": "没有匹配筛选条件的结果。",
|
|
226
|
+
"empty.traces": "没有可用追踪。保存工件的 eval run 会收集 traces。",
|
|
227
|
+
"table.experiment": "实验",
|
|
228
|
+
"table.model": "模型",
|
|
229
|
+
"table.agent": "Agent",
|
|
230
|
+
"table.avgDuration": "平均耗时",
|
|
231
|
+
"table.successRate": "成功率",
|
|
232
|
+
"table.tokens": "Tokens",
|
|
233
|
+
"table.estCost": "预估成本",
|
|
234
|
+
"table.outcomes": "结果",
|
|
235
|
+
"table.evalId": "Eval ID",
|
|
236
|
+
"table.outcome": "状态",
|
|
237
|
+
"table.ranAt": "运行时间",
|
|
238
|
+
"detail.evalResult": "个 eval",
|
|
239
|
+
"detail.evalResults": "个 eval",
|
|
240
|
+
"detail.attempts": "尝试",
|
|
241
|
+
"detail.evals": "Eval 数",
|
|
242
|
+
"detail.runs": "总轮次",
|
|
243
|
+
"detail.runsUnit": "轮",
|
|
244
|
+
"detail.passed": "通过",
|
|
245
|
+
"detail.failed": "失败",
|
|
246
|
+
"detail.errored": "错误",
|
|
247
|
+
"detail.totalTime": "总耗时",
|
|
248
|
+
"detail.totalCost": "总成本",
|
|
249
|
+
"detail.ran": "运行",
|
|
250
|
+
"detail.evaluationAttempts": "各 Eval",
|
|
251
|
+
"detail.status": "状态",
|
|
252
|
+
"detail.eval": "Eval",
|
|
253
|
+
"detail.reason": "原因",
|
|
254
|
+
"detail.time": "耗时",
|
|
255
|
+
"detail.run": "轮次",
|
|
256
|
+
"detail.rawSample": "原始样例结果",
|
|
257
|
+
"detail.rawNote": "调试 JSON,默认选择第一条错误/失败",
|
|
258
|
+
"config.experiment": "实验",
|
|
259
|
+
"config.flagsNone": "无",
|
|
260
|
+
"config.default": "默认",
|
|
261
|
+
"config.none": "无",
|
|
262
|
+
"config.notApplicable": "不适用",
|
|
263
|
+
"status.pass": "通过",
|
|
264
|
+
"status.fail": "失败",
|
|
265
|
+
"status.error": "错误",
|
|
266
|
+
"status.skipped": "跳过",
|
|
267
|
+
"action.close": "关闭",
|
|
268
|
+
"action.copyReason": "复制原因",
|
|
269
|
+
"action.copyErrors": "复制错误",
|
|
270
|
+
"action.copied": "已复制",
|
|
271
|
+
"trace.loading": "加载中...",
|
|
272
|
+
"trace.loadFailed": "加载失败(静态报告没有服务端 - 请用 niceeval view):",
|
|
273
|
+
"trace.transcript": "会话",
|
|
274
|
+
"trace.timing": "耗时追踪",
|
|
275
|
+
"trace.noSpans": "没有 span",
|
|
276
|
+
"trace.total": "总计",
|
|
277
|
+
"trace.spans": "spans",
|
|
278
|
+
"trace.clickDetails": "点击行查看详情",
|
|
279
|
+
"transcript.noEvents": "没有事件",
|
|
280
|
+
"transcript.user": "user",
|
|
281
|
+
"transcript.assistant": "assistant",
|
|
282
|
+
"transcript.thinking": "thinking",
|
|
283
|
+
"transcript.inputRequested": "请求输入",
|
|
284
|
+
"transcript.awaitingInput": "(等待输入)",
|
|
285
|
+
"transcript.contextCompacted": "上下文已压缩",
|
|
286
|
+
"transcript.running": "运行中...",
|
|
287
|
+
"transcript.input": "输入",
|
|
288
|
+
"transcript.output": "输出",
|
|
289
|
+
"transcript.empty": "(空)",
|
|
290
|
+
"code.otherAssertions": "其它断言",
|
|
291
|
+
"code.noReply": "(无回复)",
|
|
292
|
+
"code.reply": "回复",
|
|
293
|
+
"code.hide": "收起",
|
|
294
|
+
"code.checks": "检查",
|
|
295
|
+
"code.conversation": "会话",
|
|
296
|
+
"code.noSource": "源码未捕获。此 run 可能早于 source-loc,或源码不可读。重跑此 eval 即可看到代码视图。",
|
|
297
|
+
"assert.pass": "通过",
|
|
298
|
+
"assert.fail": "失败",
|
|
299
|
+
"assert.soft": "soft",
|
|
300
|
+
"assert.evidence": "裁判看到的材料",
|
|
301
|
+
"outcome.passed": "通过",
|
|
302
|
+
"outcome.failed": "失败",
|
|
303
|
+
"outcome.errored": "错误",
|
|
304
|
+
"outcome.skipped": "跳过",
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const storageKey = "niceeval:view:locale";
|
|
309
|
+
|
|
310
|
+
export function detectLocale(): Locale {
|
|
311
|
+
const stored = readStoredLocale();
|
|
312
|
+
if (stored) return stored;
|
|
313
|
+
const candidates = typeof navigator === "undefined" ? [] : [navigator.language, ...(navigator.languages ?? [])];
|
|
314
|
+
return candidates.some((value) => value.toLowerCase().startsWith("zh")) ? "zh-CN" : "en";
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function persistLocale(locale: Locale): void {
|
|
318
|
+
try {
|
|
319
|
+
localStorage.setItem(storageKey, locale);
|
|
320
|
+
} catch {
|
|
321
|
+
// Reports must still work from local files and locked-down browsers.
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function setDocumentLocale(locale: Locale): void {
|
|
326
|
+
document.documentElement.lang = locale;
|
|
327
|
+
document.title = dictionaries[locale]["app.title"];
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function makeTranslator(locale: Locale): (key: MessageKey) => string {
|
|
331
|
+
return (key) => dictionaries[locale][key];
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function readStoredLocale(): Locale | undefined {
|
|
335
|
+
try {
|
|
336
|
+
const value = localStorage.getItem(storageKey);
|
|
337
|
+
return value === "zh-CN" || value === "en" ? value : undefined;
|
|
338
|
+
} catch {
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>niceeval view (dev)</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script src="https://unpkg.com/grab/dist/index.global.js"></script>
|
|
11
|
+
<script type="module" src="./main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|