dsh-livebench-panel 0.1.5 → 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 +156 -41
- package/lib/index.js +95 -7
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -45,7 +45,12 @@ window.__ModuleLoader__.load({
|
|
|
45
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
46
|
.dlb_helpTable th{color:var(--dsw-alias-label-tertiary);font-weight:500;white-space:nowrap}
|
|
47
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}
|
|
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}`;
|
|
49
54
|
const tagId = "dsh-livebench-panel/panel.css";
|
|
50
55
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
51
56
|
const tag = document.createElement("style");
|
|
@@ -87,13 +92,18 @@ window.__ModuleLoader__.load({
|
|
|
87
92
|
function LiveBenchView() {
|
|
88
93
|
const [config, setConfig] = useState(null);
|
|
89
94
|
const [configError, setConfigError] = useState(null);
|
|
90
|
-
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" });
|
|
91
96
|
const [busy, setBusy] = useState(false);
|
|
92
97
|
const [running, setRunning] = useState(false);
|
|
93
98
|
const [log, setLog] = useState("");
|
|
94
99
|
const [exitCode, setExitCode] = useState(null);
|
|
95
100
|
const [startError, setStartError] = useState(null);
|
|
96
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);
|
|
97
107
|
const logRef = useRef(null);
|
|
98
108
|
|
|
99
109
|
const loadConfig = useCallback(async () => {
|
|
@@ -158,10 +168,6 @@ window.__ModuleLoader__.load({
|
|
|
158
168
|
}, [log]);
|
|
159
169
|
|
|
160
170
|
const categories = useMemo(() => (config ? Object.keys(config.tasks) : []), [config]);
|
|
161
|
-
const taskList = useMemo(() => {
|
|
162
|
-
if (!config || sel.category.length === 0) return [];
|
|
163
|
-
return Object.keys(config.tasks[sel.category] ?? {});
|
|
164
|
-
}, [config, sel.category]);
|
|
165
171
|
// 有效题数:LiveBench 会丢弃「发布晚于所选 release」和「在所选 release
|
|
166
172
|
// 前已退役」的题目,这里按题目桶 (发布日, 移除日) 精确复算。
|
|
167
173
|
const countFor = useCallback((category, task) => {
|
|
@@ -175,6 +181,20 @@ window.__ModuleLoader__.load({
|
|
|
175
181
|
return sum + (releasedOk && notRemoved ? b.n : 0);
|
|
176
182
|
}, 0);
|
|
177
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
|
+
});
|
|
178
198
|
const selectedProvider = useMemo(
|
|
179
199
|
() => (config ? config.providers.find((p) => p.id === sel.provider) ?? null : null),
|
|
180
200
|
[config, sel.provider],
|
|
@@ -190,8 +210,17 @@ window.__ModuleLoader__.load({
|
|
|
190
210
|
|
|
191
211
|
const onStart = async () => {
|
|
192
212
|
setStartError(null);
|
|
193
|
-
|
|
194
|
-
|
|
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。`);
|
|
195
224
|
return;
|
|
196
225
|
}
|
|
197
226
|
setBusy(true);
|
|
@@ -203,8 +232,7 @@ window.__ModuleLoader__.load({
|
|
|
203
232
|
provider: sel.provider,
|
|
204
233
|
model: sel.model,
|
|
205
234
|
reasoningEffort: sel.reasoning,
|
|
206
|
-
|
|
207
|
-
task: sel.task,
|
|
235
|
+
benchNames,
|
|
208
236
|
release: sel.release,
|
|
209
237
|
begin: sel.begin,
|
|
210
238
|
end: sel.end,
|
|
@@ -233,22 +261,65 @@ window.__ModuleLoader__.load({
|
|
|
233
261
|
const provider = config?.providers.find((p) => p.id === value) ?? null;
|
|
234
262
|
next.model = provider?.models[0]?.id ?? "";
|
|
235
263
|
}
|
|
236
|
-
if (key === "category") next.task = "";
|
|
237
264
|
return next;
|
|
238
265
|
});
|
|
239
266
|
};
|
|
240
267
|
|
|
241
|
-
//
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
+
};
|
|
252
323
|
|
|
253
324
|
return h("div", { className: c("root") },
|
|
254
325
|
h("div", { className: c("head") },
|
|
@@ -279,20 +350,30 @@ window.__ModuleLoader__.load({
|
|
|
279
350
|
h("select", { className: c("select"), value: sel.release, onChange: setField("release") },
|
|
280
351
|
(config?.releases ?? ["2024-11-25"]).map((r) => h("option", { key: r, value: r }, r))),
|
|
281
352
|
),
|
|
282
|
-
h("div", { className: c("field") },
|
|
283
|
-
h("label", { className: c("label") }, "
|
|
284
|
-
h("
|
|
285
|
-
|
|
286
|
-
|
|
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
|
+
})),
|
|
287
364
|
),
|
|
288
|
-
h("div", { className: c("field") },
|
|
289
|
-
h("label", { className: c("label") }, "
|
|
290
|
-
h("
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const n = countFor(
|
|
294
|
-
const
|
|
295
|
-
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})`);
|
|
296
377
|
})),
|
|
297
378
|
),
|
|
298
379
|
h("div", { className: c("field") },
|
|
@@ -324,18 +405,52 @@ window.__ModuleLoader__.load({
|
|
|
324
405
|
),
|
|
325
406
|
h("pre", { className: c("log"), ref: logRef }, log || "(暂无输出)"),
|
|
326
407
|
),
|
|
327
|
-
|
|
408
|
+
sortedRows.length > 0 && h("div", { className: c("card") },
|
|
328
409
|
h("div", { className: c("row") },
|
|
329
|
-
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})`),
|
|
330
416
|
),
|
|
331
417
|
h("table", { className: c("table") },
|
|
332
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"),
|
|
333
424
|
h("th", null, "model"),
|
|
334
|
-
|
|
425
|
+
h("th", null, "category"),
|
|
426
|
+
h("th", null, "task"),
|
|
427
|
+
h("th", null, "score"),
|
|
428
|
+
h("th", null, "操作"),
|
|
429
|
+
)),
|
|
335
430
|
h("tbody", null,
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
+
})),
|
|
339
454
|
),
|
|
340
455
|
),
|
|
341
456
|
config !== null && config.available === false && h("div", { className: c("card") + " " + c("notice") },
|
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",
|