dsh-livebench-panel 0.1.4 → 0.1.6
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/lib/client.js +191 -42
- package/lib/index.js +95 -7
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -36,7 +36,21 @@ window.__ModuleLoader__.load({
|
|
|
36
36
|
.dlb_notice{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 45%,transparent)}
|
|
37
37
|
.dlb_noticeTitle{margin:0;font-size:13px;font-weight:600;color:var(--dsw-alias-state-error-primary)}
|
|
38
38
|
.dlb_steps{margin:0;padding-left:18px;display:flex;flex-direction:column;gap:6px;font-size:12px;line-height:1.6;color:var(--dsw-alias-label-secondary)}
|
|
39
|
-
.dlb_code{display:block;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;color:var(--dsw-alias-label-primary);padding:4px 8px;margin-top:3px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;word-break:break-all;user-select:all}
|
|
39
|
+
.dlb_code{display:block;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;color:var(--dsw-alias-label-primary);padding:4px 8px;margin-top:3px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;word-break:break-all;user-select:all}
|
|
40
|
+
.dlb_details{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2);padding:10px 14px}
|
|
41
|
+
.dlb_details summary{cursor:pointer;font-size:12.5px;color:var(--dsw-alias-label-secondary);user-select:none}
|
|
42
|
+
.dlb_details summary:hover{color:var(--dsw-alias-label-primary)}
|
|
43
|
+
.dlb_details[open] summary{margin-bottom:10px;color:var(--dsw-alias-label-primary)}
|
|
44
|
+
.dlb_helpTable{width:100%;border-collapse:collapse;font-size:12px}
|
|
45
|
+
.dlb_helpTable th,.dlb_helpTable td{text-align:left;vertical-align:top;padding:5px 8px;border-bottom:1px solid var(--dsw-alias-border-l2)}
|
|
46
|
+
.dlb_helpTable th{color:var(--dsw-alias-label-tertiary);font-weight:500;white-space:nowrap}
|
|
47
|
+
.dlb_helpTable td{color:var(--dsw-alias-label-secondary);line-height:1.55}
|
|
48
|
+
.dlb_helpP{margin:0 0 8px;font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.6}
|
|
49
|
+
.dlb_chips{display:flex;flex-wrap:wrap;gap:6px}
|
|
50
|
+
.dlb_chip{appearance:none;font:inherit;cursor:pointer;color:var(--dsw-alias-label-secondary);background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:3px 12px;font-size:12px;line-height:18px}
|
|
51
|
+
.dlb_chip:disabled{opacity:.4;cursor:not-allowed}
|
|
52
|
+
.dlb_chipOn{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-state-business-primary);background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 14%,transparent)}
|
|
53
|
+
.dlb_drag{cursor:grab;color:var(--dsw-alias-label-tertiary);text-align:center;user-select:none}`;
|
|
40
54
|
const tagId = "dsh-livebench-panel/panel.css";
|
|
41
55
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
42
56
|
const tag = document.createElement("style");
|
|
@@ -78,13 +92,18 @@ window.__ModuleLoader__.load({
|
|
|
78
92
|
function LiveBenchView() {
|
|
79
93
|
const [config, setConfig] = useState(null);
|
|
80
94
|
const [configError, setConfigError] = useState(null);
|
|
81
|
-
const [sel, setSel] = useState({ provider: "", model: "", reasoning: "default",
|
|
95
|
+
const [sel, setSel] = useState({ provider: "", model: "", reasoning: "default", cats: [], tasks: [], release: "2024-11-25", begin: "", end: "", maxTokens: "32000" });
|
|
82
96
|
const [busy, setBusy] = useState(false);
|
|
83
97
|
const [running, setRunning] = useState(false);
|
|
84
98
|
const [log, setLog] = useState("");
|
|
85
99
|
const [exitCode, setExitCode] = useState(null);
|
|
86
100
|
const [startError, setStartError] = useState(null);
|
|
87
101
|
const [results, setResults] = useState(null);
|
|
102
|
+
const [pickedRows, setPickedRows] = useState(() => new Set());
|
|
103
|
+
const [order, setOrder] = useState(() => {
|
|
104
|
+
try { return JSON.parse(localStorage.getItem("dlb_result_order_v1")) ?? []; } catch { return []; }
|
|
105
|
+
});
|
|
106
|
+
const dragKey = useRef(null);
|
|
88
107
|
const logRef = useRef(null);
|
|
89
108
|
|
|
90
109
|
const loadConfig = useCallback(async () => {
|
|
@@ -149,10 +168,6 @@ window.__ModuleLoader__.load({
|
|
|
149
168
|
}, [log]);
|
|
150
169
|
|
|
151
170
|
const categories = useMemo(() => (config ? Object.keys(config.tasks) : []), [config]);
|
|
152
|
-
const taskList = useMemo(() => {
|
|
153
|
-
if (!config || sel.category.length === 0) return [];
|
|
154
|
-
return Object.keys(config.tasks[sel.category] ?? {});
|
|
155
|
-
}, [config, sel.category]);
|
|
156
171
|
// 有效题数:LiveBench 会丢弃「发布晚于所选 release」和「在所选 release
|
|
157
172
|
// 前已退役」的题目,这里按题目桶 (发布日, 移除日) 精确复算。
|
|
158
173
|
const countFor = useCallback((category, task) => {
|
|
@@ -166,6 +181,20 @@ window.__ModuleLoader__.load({
|
|
|
166
181
|
return sum + (releasedOk && notRemoved ? b.n : 0);
|
|
167
182
|
}, 0);
|
|
168
183
|
}, [config, sel.release]);
|
|
184
|
+
const catCount = useCallback((category) => {
|
|
185
|
+
if (!config) return 0;
|
|
186
|
+
return Object.keys(config.tasks[category] ?? {}).reduce((sum, task) => sum + (countFor(category, task) ?? 0), 0);
|
|
187
|
+
}, [config, countFor]);
|
|
188
|
+
// 多选任务 chips:未选分类时展示全部分类的任务
|
|
189
|
+
const taskChips = useMemo(() => {
|
|
190
|
+
const cats = sel.cats.length > 0 ? sel.cats : categories;
|
|
191
|
+
return cats.flatMap((cat) => Object.keys(config?.tasks[cat] ?? {}).map((task) => `${cat}/${task}`));
|
|
192
|
+
}, [sel.cats, categories, config]);
|
|
193
|
+
const toggleList = (key, value) => setSel((prev) => {
|
|
194
|
+
const list = prev[key];
|
|
195
|
+
const next = list.includes(value) ? list.filter((v) => v !== value) : [...list, value];
|
|
196
|
+
return { ...prev, [key]: next };
|
|
197
|
+
});
|
|
169
198
|
const selectedProvider = useMemo(
|
|
170
199
|
() => (config ? config.providers.find((p) => p.id === sel.provider) ?? null : null),
|
|
171
200
|
[config, sel.provider],
|
|
@@ -181,8 +210,17 @@ window.__ModuleLoader__.load({
|
|
|
181
210
|
|
|
182
211
|
const onStart = async () => {
|
|
183
212
|
setStartError(null);
|
|
184
|
-
|
|
185
|
-
|
|
213
|
+
const benchNames = [];
|
|
214
|
+
if (sel.cats.length === 0) {
|
|
215
|
+
benchNames.push("live_bench");
|
|
216
|
+
} else if (sel.tasks.length === 0) {
|
|
217
|
+
for (const cat of sel.cats) benchNames.push(`live_bench/${cat}`);
|
|
218
|
+
} else {
|
|
219
|
+
for (const t of sel.tasks) benchNames.push(`live_bench/${t}`);
|
|
220
|
+
}
|
|
221
|
+
const zeroTask = sel.tasks.find((t) => countFor(t.split("/")[0], t.split("/")[1]) === 0);
|
|
222
|
+
if (zeroTask) {
|
|
223
|
+
setStartError(`任务 ${zeroTask.split("/")[1]} 在 release ${sel.release} 下没有可用题目(该批题目已退役),请取消勾选或换 release。`);
|
|
186
224
|
return;
|
|
187
225
|
}
|
|
188
226
|
setBusy(true);
|
|
@@ -194,8 +232,7 @@ window.__ModuleLoader__.load({
|
|
|
194
232
|
provider: sel.provider,
|
|
195
233
|
model: sel.model,
|
|
196
234
|
reasoningEffort: sel.reasoning,
|
|
197
|
-
|
|
198
|
-
task: sel.task,
|
|
235
|
+
benchNames,
|
|
199
236
|
release: sel.release,
|
|
200
237
|
begin: sel.begin,
|
|
201
238
|
end: sel.end,
|
|
@@ -224,22 +261,65 @@ window.__ModuleLoader__.load({
|
|
|
224
261
|
const provider = config?.providers.find((p) => p.id === value) ?? null;
|
|
225
262
|
next.model = provider?.models[0]?.id ?? "";
|
|
226
263
|
}
|
|
227
|
-
if (key === "category") next.task = "";
|
|
228
264
|
return next;
|
|
229
265
|
});
|
|
230
266
|
};
|
|
231
267
|
|
|
232
|
-
//
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
268
|
+
// ---- 成绩行:排序(拖拽,localStorage 持久化)/ 选择 / 删除 ----
|
|
269
|
+
const rowKey = (row) => `${row.model}|${row.category}|${row.task}`;
|
|
270
|
+
const sortedRows = useMemo(() => {
|
|
271
|
+
if (!results) return [];
|
|
272
|
+
const indexOf = new Map(order.map((key, index) => [key, index]));
|
|
273
|
+
return [...results.rows].sort((a, b) => {
|
|
274
|
+
const ia = indexOf.has(rowKey(a)) ? indexOf.get(rowKey(a)) : Number.MAX_SAFE_INTEGER;
|
|
275
|
+
const ib = indexOf.has(rowKey(b)) ? indexOf.get(rowKey(b)) : Number.MAX_SAFE_INTEGER;
|
|
276
|
+
if (ia !== ib) return ia - ib;
|
|
277
|
+
return (b.time ?? 0) - (a.time ?? 0);
|
|
278
|
+
});
|
|
279
|
+
}, [results, order]);
|
|
280
|
+
|
|
281
|
+
const persistOrder = (keys) => {
|
|
282
|
+
setOrder(keys);
|
|
283
|
+
try { localStorage.setItem("dlb_result_order_v1", JSON.stringify(keys)); } catch { /* private mode */ }
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const onRowDrop = (targetKey) => {
|
|
287
|
+
const sourceKey = dragKey.current;
|
|
288
|
+
dragKey.current = null;
|
|
289
|
+
if (!sourceKey || sourceKey === targetKey) return;
|
|
290
|
+
const keys = sortedRows.map(rowKey);
|
|
291
|
+
const from = keys.indexOf(sourceKey);
|
|
292
|
+
const to = keys.indexOf(targetKey);
|
|
293
|
+
if (from < 0 || to < 0) return;
|
|
294
|
+
keys.splice(to, 0, keys.splice(from, 1)[0]);
|
|
295
|
+
persistOrder(keys);
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const togglePicked = (key) => setPickedRows((prev) => {
|
|
299
|
+
const next = new Set(prev);
|
|
300
|
+
if (next.has(key)) next.delete(key); else next.add(key);
|
|
301
|
+
return next;
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const deleteRows = async (rows) => {
|
|
305
|
+
if (rows.length === 0) return;
|
|
306
|
+
if (!window.confirm(`确认删除 ${rows.length} 条评测成绩?将同时删除对应的模型答案记录,不可恢复。`)) return;
|
|
307
|
+
await api("/delete", {
|
|
308
|
+
method: "POST",
|
|
309
|
+
headers: { "content-type": "application/json" },
|
|
310
|
+
body: JSON.stringify({ rows: rows.map((r) => ({ model: r.model, category: r.category, task: r.task })) }),
|
|
311
|
+
});
|
|
312
|
+
setPickedRows(new Set());
|
|
313
|
+
persistOrder([...order].filter((key) => !rows.some((r) => rowKey(r) === key)));
|
|
314
|
+
await loadResults();
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const fmtTime = (t) => {
|
|
318
|
+
if (!t) return "—";
|
|
319
|
+
const dt = new Date(t * 1000);
|
|
320
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
321
|
+
return `${dt.getFullYear()}-${pad(dt.getMonth() + 1)}-${pad(dt.getDate())} ${pad(dt.getHours())}:${pad(dt.getMinutes())}`;
|
|
322
|
+
};
|
|
243
323
|
|
|
244
324
|
return h("div", { className: c("root") },
|
|
245
325
|
h("div", { className: c("head") },
|
|
@@ -270,20 +350,30 @@ window.__ModuleLoader__.load({
|
|
|
270
350
|
h("select", { className: c("select"), value: sel.release, onChange: setField("release") },
|
|
271
351
|
(config?.releases ?? ["2024-11-25"]).map((r) => h("option", { key: r, value: r }, r))),
|
|
272
352
|
),
|
|
273
|
-
h("div", { className: c("field") },
|
|
274
|
-
h("label", { className: c("label") }, "
|
|
275
|
-
h("
|
|
276
|
-
|
|
277
|
-
|
|
353
|
+
h("div", { className: c("field"), style: { gridColumn: "1 / -1" } },
|
|
354
|
+
h("label", { className: c("label") }, "分类(可多选,不选 = 全部分类;括号内为该 release 下有效题数)"),
|
|
355
|
+
h("div", { className: c("chips") },
|
|
356
|
+
categories.map((cat) => {
|
|
357
|
+
const n = catCount(cat);
|
|
358
|
+
const active = sel.cats.includes(cat);
|
|
359
|
+
return h("button", {
|
|
360
|
+
key: cat, type: "button", className: c("chip") + (active ? " " + c("chipOn") : ""),
|
|
361
|
+
onClick: () => toggleList("cats", cat), disabled: n === 0,
|
|
362
|
+
}, `${cat}(${n})`);
|
|
363
|
+
})),
|
|
278
364
|
),
|
|
279
|
-
h("div", { className: c("field") },
|
|
280
|
-
h("label", { className: c("label") }, "
|
|
281
|
-
h("
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const n = countFor(
|
|
285
|
-
const
|
|
286
|
-
return h("
|
|
365
|
+
sel.cats.length > 0 && h("div", { className: c("field"), style: { gridColumn: "1 / -1" } },
|
|
366
|
+
h("label", { className: c("label") }, "任务(可多选,不选 = 所选分类的全部任务)"),
|
|
367
|
+
h("div", { className: c("chips") },
|
|
368
|
+
taskChips.map((key) => {
|
|
369
|
+
const [cat, task] = key.split("/");
|
|
370
|
+
const n = countFor(cat, task) ?? 0;
|
|
371
|
+
const active = sel.tasks.includes(key);
|
|
372
|
+
return h("button", {
|
|
373
|
+
key, type: "button", className: c("chip") + (active ? " " + c("chipOn") : ""),
|
|
374
|
+
onClick: () => toggleList("tasks", key), disabled: n === 0,
|
|
375
|
+
title: n === 0 ? "该任务在此 release 下已无可用题目" : `${n} 题有效`,
|
|
376
|
+
}, `${task}(${n})`);
|
|
287
377
|
})),
|
|
288
378
|
),
|
|
289
379
|
h("div", { className: c("field") },
|
|
@@ -315,18 +405,52 @@ window.__ModuleLoader__.load({
|
|
|
315
405
|
),
|
|
316
406
|
h("pre", { className: c("log"), ref: logRef }, log || "(暂无输出)"),
|
|
317
407
|
),
|
|
318
|
-
|
|
408
|
+
sortedRows.length > 0 && h("div", { className: c("card") },
|
|
319
409
|
h("div", { className: c("row") },
|
|
320
|
-
h("span", { className: c("label") }, "评测成绩(分数 = 平均分 ×100
|
|
410
|
+
h("span", { className: c("label") }, "评测成绩(分数 = 平均分 ×100;行首可拖动排序,顺序保存在本机)"),
|
|
411
|
+
h("button", {
|
|
412
|
+
className: c("btn") + " " + c("btnDanger"),
|
|
413
|
+
disabled: pickedRows.size === 0,
|
|
414
|
+
onClick: () => deleteRows(sortedRows.filter((r) => pickedRows.has(rowKey(r)))),
|
|
415
|
+
}, `删除所选(${pickedRows.size})`),
|
|
321
416
|
),
|
|
322
417
|
h("table", { className: c("table") },
|
|
323
418
|
h("thead", null, h("tr", null,
|
|
419
|
+
h("th", { style: { width: "30px" } }, "⠿"),
|
|
420
|
+
h("th", { style: { width: "30px" } },
|
|
421
|
+
h("input", { type: "checkbox", checked: pickedRows.size > 0 && pickedRows.size === sortedRows.length,
|
|
422
|
+
onChange: (event) => setPickedRows(event.target.checked ? new Set(sortedRows.map(rowKey)) : new Set()) })),
|
|
423
|
+
h("th", null, "time"),
|
|
324
424
|
h("th", null, "model"),
|
|
325
|
-
|
|
425
|
+
h("th", null, "category"),
|
|
426
|
+
h("th", null, "task"),
|
|
427
|
+
h("th", null, "score"),
|
|
428
|
+
h("th", null, "操作"),
|
|
429
|
+
)),
|
|
326
430
|
h("tbody", null,
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
431
|
+
sortedRows.map((row) => {
|
|
432
|
+
const key = rowKey(row);
|
|
433
|
+
const checked = pickedRows.has(key);
|
|
434
|
+
return h("tr", {
|
|
435
|
+
key,
|
|
436
|
+
draggable: true,
|
|
437
|
+
onDragStart: () => { dragKey.current = key; },
|
|
438
|
+
onDragOver: (event) => event.preventDefault(),
|
|
439
|
+
onDrop: () => onRowDrop(key),
|
|
440
|
+
},
|
|
441
|
+
h("td", { className: c("drag"), title: "拖动排序" }, "⠿"),
|
|
442
|
+
h("td", null, h("input", { type: "checkbox", checked, onChange: () => togglePicked(key) })),
|
|
443
|
+
h("td", null, fmtTime(row.time)),
|
|
444
|
+
h("td", null, row.model),
|
|
445
|
+
h("td", null, row.category),
|
|
446
|
+
h("td", null, row.task),
|
|
447
|
+
h("td", { className: c("score") }, `${row.score.toFixed(1)} (${row.judged}/${row.total})`),
|
|
448
|
+
h("td", null, h("button", {
|
|
449
|
+
className: c("btnGhost") + " " + c("btn"), title: "删除此条成绩",
|
|
450
|
+
onClick: () => deleteRows([row]),
|
|
451
|
+
}, "删除")),
|
|
452
|
+
);
|
|
453
|
+
})),
|
|
330
454
|
),
|
|
331
455
|
),
|
|
332
456
|
config !== null && config.available === false && h("div", { className: c("card") + " " + c("notice") },
|
|
@@ -347,7 +471,32 @@ window.__ModuleLoader__.load({
|
|
|
347
471
|
h("br", null),
|
|
348
472
|
"若装在其它目录:设置系统环境变量 ", h("code", { className: c("code") }, "DSH_LIVEBENCH_HOME=你的LiveBench目录"), " 后再重启 dsh web。"),
|
|
349
473
|
),
|
|
350
|
-
h("p", { className: c("hint") },
|
|
474
|
+
h("p", { className: c("hint") },
|
|
475
|
+
"除 LiveBench 外无需其它配置:评测所需的 API Key 会自动从 harness 凭据库读取并注入;npm 安装本插件时可一并执行 dsh plugin --profile web add dsh-livebench-panel。完整说明见插件目录内 README:~\\.dsh\\plugins\\dsh-livebench-panel\\README.md"),
|
|
476
|
+
),
|
|
477
|
+
h("details", { className: c("details") },
|
|
478
|
+
h("summary", null, "❓ 使用说明 · 选项含义与选择建议(点击展开)"),
|
|
479
|
+
h("p", { className: c("helpP") },
|
|
480
|
+
"操作流程:选择参数 → 点「开始评测」→ 日志区实时显示运行进度 → 结束后成绩表自动刷新(运行中可「停止」)。全部判分均为客观比对(文本/符号执行/测试用例),不使用 AI 评分。"),
|
|
481
|
+
h("table", { className: c("helpTable") },
|
|
482
|
+
h("thead", null, h("tr", null, h("th", null, "选项"), h("th", null, "含义与选择建议"))),
|
|
483
|
+
h("tbody", null,
|
|
484
|
+
h("tr", null, h("th", null, "模型"), h("td", null,
|
|
485
|
+
"harness 全部 provider 的全部模型(含内置 DeepSeek 官方)。API Key 自动从 harness 凭据库读取并注入,无需手工配置;名字带「·未接LiveBench」的 provider 无法自动路由,评测会按模型名原生尝试。")),
|
|
486
|
+
h("tr", null, h("th", null, "推理强度"), h("td", null,
|
|
487
|
+
"模型的思考深度(off=关闭思考)。强度会编码进条目名(如 glm-5.3@max),不同强度在成绩表中是独立条目,方便对比。想省成本选 low,追求质量选 high/max。")),
|
|
488
|
+
h("tr", null, h("th", null, "题集 release"), h("td", null,
|
|
489
|
+
"题目发布批次。推荐 2024-11-25(公开题目最全)。LiveBench 每月换题:新批次下老任务会陆续退役,任务下拉括号内就是该批次下的有效题数。")),
|
|
490
|
+
h("tr", null, h("th", null, "分类 / 任务"), h("td", null,
|
|
491
|
+
"六大类共 18 个任务:coding(代码生成/补全)、math(竞赛数学等)、reasoning(空间推理/逻辑谜题)、language(拼写/连线/语义)、data_analysis(表格操作)、instruction_following(指令遵循)。首次验证推荐 language → typos。")),
|
|
492
|
+
h("tr", null, h("th", null, "题目序号范围"), h("td", null,
|
|
493
|
+
"从 0 起。冒烟测试填 0 到 2(只跑 3 题);2 题得分噪声很大(对一题就是 0↔100 的波动),想看真实水平建议 20 题以上。")),
|
|
494
|
+
h("tr", null, h("th", null, "max-tokens"), h("td", null,
|
|
495
|
+
"单题回答的 token 上限,默认 32000。推理模型思考也占 token,不要低于 8192,否则思考被截断、答案为空会记 0 分。")),
|
|
496
|
+
),
|
|
497
|
+
),
|
|
498
|
+
h("p", { className: c("helpP"), style: { marginTop: "10px" } },
|
|
499
|
+
"提示:回答为 $ERROR$ 表示该题 API 调用失败(网络/鉴权/参数问题)计 0 分,可在上方日志区查看具体错误;zebra_puzzle(逻辑谜题)是公认最难的任务,低分属正常现象。"),
|
|
351
500
|
),
|
|
352
501
|
h("p", { className: c("hint") },
|
|
353
502
|
"评分读取 LiveBench 的 ground_truth_judgment.jsonl;正式榜单可用 release 2024-11-25。多选题集请在「分类/任务」中缩小范围,避免长时间运行。"),
|
package/lib/index.js
CHANGED
|
@@ -365,7 +365,7 @@ function readJsonl(path) {
|
|
|
365
365
|
* files, plus the judged/total question counts per task.
|
|
366
366
|
*/
|
|
367
367
|
function computeResults(dataDir) {
|
|
368
|
-
const judged = new Map(); // `${model}\u0000${task}` -> {model, category, task, sum, n}
|
|
368
|
+
const judged = new Map(); // `${model}\u0000${task}` -> {model, category, task, sum, n, time}
|
|
369
369
|
const totals = new Map(); // task -> question count
|
|
370
370
|
if (!existsSync(dataDir)) return { rows: [], taskTotals: {} };
|
|
371
371
|
for (const category of readDirSafe(dataDir)) {
|
|
@@ -380,9 +380,11 @@ function computeResults(dataDir) {
|
|
|
380
380
|
const score = typeof row.score === "number" ? row.score : Number(row.score);
|
|
381
381
|
if (model === null || !Number.isFinite(score) || score < 0) continue;
|
|
382
382
|
const key = `${model}\u0000${task}`;
|
|
383
|
-
const entry = judged.get(key) ?? { model, category, task, sum: 0, n: 0 };
|
|
383
|
+
const entry = judged.get(key) ?? { model, category, task, sum: 0, n: 0, time: 0 };
|
|
384
384
|
entry.sum += score;
|
|
385
385
|
entry.n += 1;
|
|
386
|
+
const tstamp = Number(row.tstamp);
|
|
387
|
+
if (Number.isFinite(tstamp) && tstamp > entry.time) entry.time = tstamp;
|
|
386
388
|
judged.set(key, entry);
|
|
387
389
|
}
|
|
388
390
|
}
|
|
@@ -394,11 +396,38 @@ function computeResults(dataDir) {
|
|
|
394
396
|
score: entry.n > 0 ? (entry.sum / entry.n) * 100 : 0,
|
|
395
397
|
judged: entry.n,
|
|
396
398
|
total: totals.get(entry.task) ?? 0,
|
|
399
|
+
time: entry.time > 0 ? entry.time : null,
|
|
397
400
|
}));
|
|
398
401
|
const taskTotals = Object.fromEntries(totals);
|
|
399
402
|
return { rows, taskTotals };
|
|
400
403
|
}
|
|
401
404
|
|
|
405
|
+
/**
|
|
406
|
+
* Remove every JSONL line of one model from a file (matched on the given
|
|
407
|
+
* field), rewriting the file only when something changed.
|
|
408
|
+
* @returns {number} removed line count.
|
|
409
|
+
*/
|
|
410
|
+
function filterJsonlByModel(path, field, model) {
|
|
411
|
+
if (!existsSync(path)) return 0;
|
|
412
|
+
const lines = readFileSync(path, "utf8").split(/\r?\n/);
|
|
413
|
+
const kept = [];
|
|
414
|
+
let removed = 0;
|
|
415
|
+
for (const line of lines) {
|
|
416
|
+
if (line.trim().length === 0) continue;
|
|
417
|
+
let match = false;
|
|
418
|
+
try {
|
|
419
|
+
const parsed = JSON.parse(line);
|
|
420
|
+
match = parsed[field] === model;
|
|
421
|
+
} catch {
|
|
422
|
+
match = false;
|
|
423
|
+
}
|
|
424
|
+
if (match) removed += 1;
|
|
425
|
+
else kept.push(line);
|
|
426
|
+
}
|
|
427
|
+
if (removed > 0) writeFileSync(path, kept.length > 0 ? kept.join("\n") + "\n" : "", "utf8");
|
|
428
|
+
return removed;
|
|
429
|
+
}
|
|
430
|
+
|
|
402
431
|
/** Clamp helper for query params. */
|
|
403
432
|
function asInt(value, min, max, fallback) {
|
|
404
433
|
const n = Number(value);
|
|
@@ -438,10 +467,28 @@ function apply(ctx) {
|
|
|
438
467
|
const providers = readProviders(profileDir);
|
|
439
468
|
const provider = providers.find((p) => p.id === providerId) ?? null;
|
|
440
469
|
|
|
441
|
-
//
|
|
442
|
-
//
|
|
443
|
-
//
|
|
444
|
-
|
|
470
|
+
// Multi-select support: the client resolves its category/task chips into
|
|
471
|
+
// an explicit bench-path list ("live_bench", "live_bench/<cat>",
|
|
472
|
+
// "live_bench/<cat>/<task>"); gen_api_answer accepts nargs="+".
|
|
473
|
+
let benchNames = null;
|
|
474
|
+
if (Array.isArray(body.benchNames) && body.benchNames.length > 0) {
|
|
475
|
+
if (body.benchNames.length > 24) {
|
|
476
|
+
return { status: 400, payload: { ok: false, error: "too many bench names (max 24)" } };
|
|
477
|
+
}
|
|
478
|
+
benchNames = [];
|
|
479
|
+
for (const bn of body.benchNames) {
|
|
480
|
+
if (typeof bn !== "string") return { status: 400, payload: { ok: false, error: "invalid bench name" } };
|
|
481
|
+
const parts = bn.split("/").filter((s) => s.length > 0);
|
|
482
|
+
if (parts[0] !== "live_bench" || parts.length > 3) {
|
|
483
|
+
return { status: 400, payload: { ok: false, error: `invalid bench path: ${bn}` } };
|
|
484
|
+
}
|
|
485
|
+
for (const seg of parts) {
|
|
486
|
+
if (!/^[a-z0-9_]+$/.test(seg)) return { status: 400, payload: { ok: false, error: `invalid bench path: ${bn}` } };
|
|
487
|
+
}
|
|
488
|
+
benchNames.push(parts.join("/"));
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
445
492
|
const hasEffortSuffix = reasoningEffort !== null && reasoningEffort !== "off";
|
|
446
493
|
const displayName = displayModelName(providerId || "direct", modelId) + (hasEffortSuffix ? "@" + reasoningEffort : "");
|
|
447
494
|
let cliModel = modelId;
|
|
@@ -456,7 +503,8 @@ function apply(ctx) {
|
|
|
456
503
|
"run_livebench.py",
|
|
457
504
|
"--model", cliModel,
|
|
458
505
|
"--model-display-name", displayName,
|
|
459
|
-
"--bench-name",
|
|
506
|
+
"--bench-name",
|
|
507
|
+
...(benchNames !== null ? benchNames : [benchParts.join("/")]),
|
|
460
508
|
"--livebench-release-option", release,
|
|
461
509
|
"--max-tokens", String(asInt(body.maxTokens, 256, 32768, 32000)),
|
|
462
510
|
"--parallel-requests", String(asInt(body.parallel, 1, 8, 2)),
|
|
@@ -669,6 +717,46 @@ function apply(ctx) {
|
|
|
669
717
|
sendJson(res, 200, { ok: true, rows, taskTotals });
|
|
670
718
|
},
|
|
671
719
|
}), `${name}: results route`);
|
|
720
|
+
|
|
721
|
+
ctx.effect(() => ctx.webServer.register({
|
|
722
|
+
kind: "exact",
|
|
723
|
+
path: `${API}/delete`,
|
|
724
|
+
handler: async (req, res) => {
|
|
725
|
+
if (req.method !== "POST") {
|
|
726
|
+
sendJson(res, 405, { ok: false, error: "method not allowed" });
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
if (!originAllowed(req)) {
|
|
730
|
+
sendJson(res, 403, { ok: false, error: "forbidden origin" });
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
let body;
|
|
734
|
+
try {
|
|
735
|
+
body = JSON.parse(await readBody(req));
|
|
736
|
+
} catch {
|
|
737
|
+
sendJson(res, 400, { ok: false, error: "invalid request body" });
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
const rows = Array.isArray(body.rows) ? body.rows.slice(0, 200) : [];
|
|
741
|
+
const layout = livebenchLayout();
|
|
742
|
+
const tasks = scanCategoryTasks(layout.dataDir);
|
|
743
|
+
let removed = 0;
|
|
744
|
+
for (const row of rows) {
|
|
745
|
+
if (!row || typeof row !== "object") continue;
|
|
746
|
+
const model = typeof row.model === "string" ? row.model : "";
|
|
747
|
+
const category = typeof row.category === "string" ? row.category : "";
|
|
748
|
+
const task = typeof row.task === "string" ? row.task : "";
|
|
749
|
+
// path-safety: membership in the scanned task list + charset guards
|
|
750
|
+
if (!/^[A-Za-z0-9._@-]{1,160}$/.test(model)) continue;
|
|
751
|
+
if (!/^[a-z0-9_]{1,60}$/.test(category) || !/^[a-z0-9_]{1,60}$/.test(task)) continue;
|
|
752
|
+
if (!(tasks[category] && tasks[category][task])) continue;
|
|
753
|
+
const taskDir = join(layout.dataDir, category, task);
|
|
754
|
+
removed += filterJsonlByModel(join(taskDir, "model_answer", `${model}.jsonl`), "model_id", model);
|
|
755
|
+
removed += filterJsonlByModel(join(taskDir, "model_judgment", "ground_truth_judgment.jsonl"), "model", model);
|
|
756
|
+
}
|
|
757
|
+
sendJson(res, 200, { ok: true, removed });
|
|
758
|
+
},
|
|
759
|
+
}), `${name}: delete route`);
|
|
672
760
|
}
|
|
673
761
|
|
|
674
762
|
/** Count alive runs (at most one today, kept for future parallel lanes). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-livebench-panel",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "DSH web plugin: a LiveBench tab in the Trajectory view (right of 对话/轨迹). Run LiveBench evaluations against every model configured in the DeepSeek Harness — pick provider/model, category, task, release and question range from dropdowns, watch progress, and read scores in place.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|