Skip to content

Commit 62befa0

Browse files
committed
feat(docs-site): Benchmarks page — port PR #144 Frontier boards to the docs site
The GUI was the wrong home for 2.6k lines of hand-maintained leaderboard snapshots plus a new ECharts runtime. The Astro docs site (GitHub Pages, slow-update-tolerant) is the natural surface: - docs-site Benchmarks page in en/ko/zh-cn, sidebar entry under Guides - FrontierBoards.astro: per-board provenance (url/capturedAt/license), costKind honesty note, vanilla ECharts cost-vs-score scatter (no React), data table with score/$ only when every row is source-measured - data + i18n (104 keys x 3 locales) extracted from PR #144; German dropped (site ships en/ko/zh-CN); refresh checklist in src/data/README-frontier.md - echarts added to docs-site only Verified: bun run build (58 pages), headless Chrome render of en + ko pages (charts draw, honesty notes and best-value badge show), zh-cn markup check. Data and concept credited to Wibias (PR #144).
1 parent 4f0ce85 commit 62befa0

11 files changed

Lines changed: 3343 additions & 1 deletion

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# 260720_frontier_docs_site — PR #144 Frontier 벤치마크를 docs-site로 이식
2+
3+
## Objective
4+
5+
PR #144(GUI Frontier 페이지, +5,421줄)를 GUI에 머지하는 대신, 정적 데이터
6+
특성에 맞는 docs-site(Astro Starlight, GitHub Pages)의 Benchmarks 페이지로
7+
이식한다. GUI에는 React+ECharts 런타임과 수동 데이터 갱신 부담을 남기지 않는다.
8+
9+
## 결정 근거
10+
11+
- diff의 절반(2,665줄)이 정적 스냅샷 JSON — 문서 사이트가 자연스러운 거처.
12+
- docs-site는 업데이트 주기가 느려도 되는 표면(사용자 결정, 2026-07-20).
13+
- GUI 런타임에 echarts 의존성 추가 회피. docs-site에만 echarts 추가(번들,
14+
React 없이 바닐라 API).
15+
16+
## 범위
17+
18+
- IN: `frontier-benchmarks.json` 복사, i18n(en/ko/zh) 추출, FrontierBoards.astro
19+
(보드별 산점도 차트 + provenance/costKind 정직성 표기 + 측정 통일 시에만 점수/$
20+
컬럼), 3개 로캘 MDX, 사이드바, 갱신 절차 문서, astro build + 렌더 검증.
21+
- OUT: GUI 변경 일체, PR #144의 react-doctor 워크플로우 변경, 5종 차트 중
22+
산점도 외(cost stack/score/efficiency/reasoning), 도메인/필터 UI.
23+
- 데이터 출처: PR #144 (Wibias) — 이식 사실을 #144에 코멘트로 남긴다.
24+
25+
## 검증
26+
27+
- `bun run build` (docs-site) 성공.
28+
- `astro preview` + 브라우저로 #benchmarks 페이지 렌더 확인(차트 표시, 3 로캘).
29+
- 배포: dev → preview → main FF 머지(deploy-docs가 main push 시 자동 배포).

docs-site/astro.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export default defineConfig({
121121
{ label: "Sidecars: Web Search & Vision", translations: { ko: "사이드카: 웹 검색 & 비전", "zh-CN": "边车:网络搜索与视觉" }, slug: "guides/sidecars" },
122122
{ label: "Web Dashboard", translations: { ko: "웹 대시보드", "zh-CN": "网页控制台" }, slug: "guides/web-dashboard" },
123123
{ label: "Sub-agent Surface", translations: { ko: "서브에이전트 서피스", "zh-CN": "子代理界面" }, slug: "guides/sub-agent-surface" },
124+
{ label: "Benchmarks", translations: { ko: "벤치마크", "zh-CN": "基准测试" }, slug: "guides/benchmarks" },
124125
],
125126
},
126127
{

docs-site/bun.lock

Lines changed: 14 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs-site/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"@astrojs/starlight": "^0.41.1",
1515
"@fontsource-variable/geist": "^5.2.9",
1616
"astro": "^7.0.3",
17+
"echarts": "^6.1.0",
1718
"pretendard": "^1.3.9",
1819
"sharp": "^0.35.2",
1920
"simple-icons": "^16.25.0"
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
---
2+
/**
3+
* Static benchmark boards (ported from PR #144's GUI Frontier page).
4+
* Renders one section per board: provenance line, honesty note for non-measured
5+
* cost figures, an ECharts cost-vs-score scatter (client-side, vanilla API), and
6+
* a data table. Score-per-dollar columns appear ONLY when every row carries a
7+
* source-measured cost (costKind honesty rule from the PR).
8+
*/
9+
import data from "../data/frontier-benchmarks.json";
10+
import { FRONTIER_STRINGS } from "../data/frontier-i18n";
11+
12+
interface Props {
13+
locale?: "en" | "ko" | "zh-cn";
14+
}
15+
16+
const { locale = "en" } = Astro.props;
17+
const strings = FRONTIER_STRINGS[locale] as Record<string, string>;
18+
const fallback = FRONTIER_STRINGS.en as Record<string, string>;
19+
const t = (key: string): string => strings[key] ?? fallback[key] ?? key;
20+
const fill = (key: string, vars: Record<string, string>): string =>
21+
Object.entries(vars).reduce((acc, [k, v]) => acc.replace(`{${k}}`, v), t(key));
22+
23+
// The PR i18n has no column-header key for "score" — keep a tiny local map.
24+
const SCORE_HEADER: Record<string, string> = { en: "Score", ko: "점수", "zh-cn": "得分" };
25+
/** Dataset tags are kebab-case; i18n keys are camelCase (cheap-subagent → cheapSubagent). */
26+
const tagKey = (tag: string): string => tag.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
27+
28+
const boards = data.benchmarks.map(board => {
29+
const allMeasured = board.rows.every(row => row.costKind === "measured");
30+
const bestValue = allMeasured
31+
? board.rows.reduce((best, row) => (row.score / row.avgCostUsd > best.score / best.avgCostUsd ? row : best), board.rows[0])
32+
: null;
33+
const chart = {
34+
id: board.id,
35+
xLabel: t(`frontier.board.${board.id}.xLabel`),
36+
yLabel: t(`frontier.board.${board.id}.yLabel`),
37+
xUnit: board.axes.xUnit,
38+
yUnit: board.axes.yUnit,
39+
rows: board.rows.map(row => ({
40+
model: row.model,
41+
family: row.family,
42+
effort: row.effort ?? null,
43+
score: row.score,
44+
cost: row.avgCostUsd,
45+
})),
46+
};
47+
return { ...board, allMeasured, bestValue, chart };
48+
});
49+
---
50+
51+
<div class="frontier-root">
52+
<p class="frontier-subtitle">{t("frontier.subtitle")}</p>
53+
{
54+
boards.map(board => (
55+
<section class="frontier-board" aria-label={t(`frontier.board.${board.id}.title`)}>
56+
<h3 id={`board-${board.id}`}>{t(`frontier.board.${board.id}.title`)}</h3>
57+
<p class="frontier-meta">
58+
{fill("frontier.updated", { date: board.updated })}
59+
{" · "}
60+
{fill("frontier.tasks", { count: String(board.taskCount) })}
61+
{" · "}
62+
<a href={board.provenance.url}>{t("frontier.sourceLink")}</a>
63+
{board.provenance.license && <span class="frontier-license"> · {board.provenance.license}</span>}
64+
</p>
65+
<p class="frontier-note">{t(`frontier.board.${board.id}.sourceNote`)}</p>
66+
{!board.allMeasured && <p class="frontier-honesty">{t("frontier.costKindEstimated")}</p>}
67+
<div class="frontier-chart" role="img" aria-label={t("frontier.chartAria")} data-chart={JSON.stringify(board.chart)} />
68+
<table>
69+
<thead>
70+
<tr>
71+
<th>{t("frontier.col.model")}</th>
72+
<th>{t("frontier.col.effort")}</th>
73+
<th>{SCORE_HEADER[locale] ?? SCORE_HEADER.en}</th>
74+
<th>{t("frontier.col.cost")}</th>
75+
{board.allMeasured && <th>{t("frontier.col.efficiency")}</th>}
76+
<th>{t("frontier.col.tags")}</th>
77+
</tr>
78+
</thead>
79+
<tbody>
80+
{board.rows.map(row => (
81+
<tr class={board.bestValue && row.id === board.bestValue.id ? "frontier-best" : undefined}>
82+
<td>
83+
{row.model}
84+
{board.bestValue && row.id === board.bestValue.id && (
85+
<span class="frontier-best-badge">{t("frontier.bestValue")}</span>
86+
)}
87+
</td>
88+
<td>{row.effort ?? ""}</td>
89+
<td>
90+
{row.score}
91+
{board.axes.yUnit === "%" ? "%" : ""}
92+
{row.scoreCi ? ` ±${row.scoreCi}` : ""}
93+
</td>
94+
<td>${row.avgCostUsd}</td>
95+
{board.allMeasured && <td>{(row.score / row.avgCostUsd).toFixed(1)}</td>}
96+
<td>{(row.tags ?? []).map(tag => t(`frontier.tag.${tagKey(tag)}`)).join(", ") || ""}</td>
97+
</tr>
98+
))}
99+
</tbody>
100+
</table>
101+
</section>
102+
))
103+
}
104+
</div>
105+
106+
<style>
107+
.frontier-root .frontier-subtitle {
108+
color: var(--sl-color-gray-3);
109+
}
110+
.frontier-board {
111+
margin-top: 2.5rem;
112+
}
113+
.frontier-meta,
114+
.frontier-note {
115+
font-size: var(--sl-text-sm);
116+
color: var(--sl-color-gray-3);
117+
margin: 0.25rem 0;
118+
}
119+
.frontier-honesty {
120+
font-size: var(--sl-text-sm);
121+
border-left: 3px solid var(--sl-color-orange);
122+
padding-left: 0.75rem;
123+
color: var(--sl-color-gray-2);
124+
}
125+
.frontier-chart {
126+
width: 100%;
127+
height: 340px;
128+
margin: 1rem 0;
129+
}
130+
.frontier-best td {
131+
font-weight: 600;
132+
}
133+
.frontier-best-badge {
134+
margin-left: 0.5rem;
135+
font-size: var(--sl-text-xs);
136+
font-weight: 600;
137+
color: var(--sl-color-accent);
138+
border: 1px solid var(--sl-color-accent);
139+
border-radius: 999px;
140+
padding: 0 0.5rem;
141+
}
142+
</style>
143+
144+
<script>
145+
import * as echarts from "echarts";
146+
147+
const FAMILY_COLORS: Record<string, string> = {
148+
openai: "#10a37f",
149+
anthropic: "#d97757",
150+
xai: "#1da1f2",
151+
google: "#4285f4",
152+
moonshot: "#7c3aed",
153+
zhipu: "#0ea5e9",
154+
cursor: "#f59e0b",
155+
deepseek: "#2563eb",
156+
other: "#6b7280",
157+
};
158+
159+
interface ChartRow {
160+
model: string;
161+
family: string;
162+
effort: string | null;
163+
score: number;
164+
cost: number;
165+
}
166+
interface ChartConfig {
167+
id: string;
168+
xLabel: string;
169+
yLabel: string;
170+
xUnit: string;
171+
yUnit: string;
172+
rows: ChartRow[];
173+
}
174+
175+
const charts: echarts.ECharts[] = [];
176+
177+
function renderAll(): void {
178+
const textColor = getComputedStyle(document.body).color;
179+
document.querySelectorAll<HTMLElement>(".frontier-chart").forEach(el => {
180+
const cfg = JSON.parse(el.dataset.chart ?? "{}") as ChartConfig;
181+
const families = [...new Set(cfg.rows.map(r => r.family))];
182+
let chart = echarts.getInstanceByDom(el);
183+
if (!chart) {
184+
chart = echarts.init(el);
185+
charts.push(chart);
186+
}
187+
chart.setOption(
188+
{
189+
textStyle: { color: textColor },
190+
grid: { left: 56, right: 24, top: 32, bottom: 48 },
191+
legend: { top: 0, textStyle: { color: textColor } },
192+
tooltip: {
193+
trigger: "item",
194+
formatter: (p: { data: [number, number, string, string, string | null] }) =>
195+
`${p.data[3]}${p.data[4] ? ` (${p.data[4]})` : ""}<br/>` +
196+
`${cfg.yLabel}: ${p.data[1]}${cfg.yUnit === "%" ? "%" : ""}<br/>` +
197+
`${cfg.xLabel}: $${p.data[0]}`,
198+
},
199+
xAxis: {
200+
type: "value",
201+
scale: true,
202+
name: cfg.xLabel,
203+
nameLocation: "middle",
204+
nameGap: 30,
205+
axisLabel: { color: textColor },
206+
nameTextStyle: { color: textColor },
207+
},
208+
yAxis: {
209+
type: "value",
210+
scale: true,
211+
name: cfg.yLabel,
212+
axisLabel: { color: textColor },
213+
nameTextStyle: { color: textColor },
214+
},
215+
series: families.map(family => ({
216+
name: family,
217+
type: "scatter",
218+
symbolSize: 11,
219+
itemStyle: { color: FAMILY_COLORS[family] ?? FAMILY_COLORS.other },
220+
data: cfg.rows
221+
.filter(r => r.family === family)
222+
.map(r => [r.cost, r.score, r.family, r.model, r.effort]),
223+
})),
224+
},
225+
{ notMerge: true },
226+
);
227+
});
228+
}
229+
230+
renderAll();
231+
window.addEventListener("resize", () => charts.forEach(c => c.resize()));
232+
// Starlight flips data-theme on <html> — re-render so axis/legend colors follow.
233+
new MutationObserver(() => renderAll()).observe(document.documentElement, {
234+
attributes: true,
235+
attributeFilter: ["data-theme"],
236+
});
237+
</script>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
title: Benchmarks
3+
description: Public coding-agent benchmark snapshots — capability vs cost per task, with per-board provenance.
4+
---
5+
6+
import FrontierBoards from "../../../components/FrontierBoards.astro";
7+
8+
These are **static snapshots** of public leaderboards, refreshed manually — not live
9+
OpenCodex metering. Each board lists its source, capture date, and license note.
10+
Score-per-dollar rankings only appear on boards where every row carries a
11+
source-measured cost per task.
12+
13+
<FrontierBoards locale="en" />
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
title: 벤치마크
3+
description: 공개 코딩 에이전트 벤치마크 스냅샷 — 작업당 비용 대비 능력, 보드별 출처 표기.
4+
---
5+
6+
import FrontierBoards from "../../../../components/FrontierBoards.astro";
7+
8+
공개 리더보드를 **수동으로 갱신하는 정적 스냅샷**입니다 — OpenCodex 실시간
9+
미터링이 아닙니다. 보드마다 출처, 캡처 날짜, 라이선스 메모를 달았고, 점수/$
10+
순위는 모든 행에 출처가 측정한 작업당 비용이 있는 보드에서만 표시합니다.
11+
12+
<FrontierBoards locale="ko" />
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
title: 基准测试
3+
description: 公开编程智能体基准快照 — 每任务成本与能力对比,附各榜单来源说明。
4+
---
5+
6+
import FrontierBoards from "../../../../components/FrontierBoards.astro";
7+
8+
这些是公开榜单的**静态快照**,手动更新 — 并非 OpenCodex 的实时计量。每个榜单都
9+
标注了来源、抓取日期和许可说明。只有当榜单中所有行都带有来源实测的每任务成本时,
10+
才会显示得分/$ 排名。
11+
12+
<FrontierBoards locale="zh-cn" />

0 commit comments

Comments
 (0)