dsh-livebench-panel 0.1.6 → 0.1.7

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.
Files changed (2) hide show
  1. package/lib/client.js +73 -48
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -99,9 +99,9 @@ window.__ModuleLoader__.load({
99
99
  const [exitCode, setExitCode] = useState(null);
100
100
  const [startError, setStartError] = useState(null);
101
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 []; }
102
+ const [pickedTasks, setPickedTasks] = useState(() => new Set());
103
+ const [taskOrder, setTaskOrder] = useState(() => {
104
+ try { return JSON.parse(localStorage.getItem("dlb_task_order_v1")) ?? []; } catch { return []; }
105
105
  });
106
106
  const dragKey = useRef(null);
107
107
  const logRef = useRef(null);
@@ -265,52 +265,78 @@ window.__ModuleLoader__.load({
265
265
  });
266
266
  };
267
267
 
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;
268
+ // ---- 成绩矩阵:task 为行(含 category 行标)、model 为列 ----
269
+ // 附带:time 列(该任务评测开始时间 = 行内最早判分时间)、
270
+ // 行拖拽排序(localStorage 持久化)、行选择与单行/批量删除。
271
+ const taskKey = (category, task) => `${category}/${task}`;
272
+ const matrix = useMemo(() => {
273
+ if (!results) return null;
274
+ const models = [...new Set(results.rows.map((r) => r.model))].sort();
275
+ const byTask = new Map(); // `${cat}/${task}` -> {category, task, cells: Map(model->row), startTime}
276
+ for (const row of results.rows) {
277
+ const key = taskKey(row.category, row.task);
278
+ const entry = byTask.get(key) ?? { category: row.category, task: row.task, cells: new Map(), startTime: Infinity };
279
+ entry.cells.set(row.model, row);
280
+ if (row.time && row.time < entry.startTime) entry.startTime = row.time;
281
+ byTask.set(key, entry);
282
+ }
283
+ return { models, tasks: [...byTask.entries()] };
284
+ }, [results]);
285
+ const sortedTasks = useMemo(() => {
286
+ if (!matrix) return [];
287
+ const indexOf = new Map(taskOrder.map((key, index) => [key, index]));
288
+ return [...matrix.tasks].sort((a, b) => {
289
+ const ia = indexOf.has(a[0]) ? indexOf.get(a[0]) : Number.MAX_SAFE_INTEGER;
290
+ const ib = indexOf.has(b[0]) ? indexOf.get(b[0]) : Number.MAX_SAFE_INTEGER;
276
291
  if (ia !== ib) return ia - ib;
277
- return (b.time ?? 0) - (a.time ?? 0);
292
+ return a[0].localeCompare(b[0]);
278
293
  });
279
- }, [results, order]);
294
+ }, [matrix, taskOrder]);
280
295
 
281
- const persistOrder = (keys) => {
282
- setOrder(keys);
283
- try { localStorage.setItem("dlb_result_order_v1", JSON.stringify(keys)); } catch { /* private mode */ }
296
+ const persistTaskOrder = (keys) => {
297
+ setTaskOrder(keys);
298
+ try { localStorage.setItem("dlb_task_order_v1", JSON.stringify(keys)); } catch { /* private mode */ }
284
299
  };
285
300
 
286
301
  const onRowDrop = (targetKey) => {
287
302
  const sourceKey = dragKey.current;
288
303
  dragKey.current = null;
289
304
  if (!sourceKey || sourceKey === targetKey) return;
290
- const keys = sortedRows.map(rowKey);
305
+ const keys = sortedTasks.map(([key]) => key);
291
306
  const from = keys.indexOf(sourceKey);
292
307
  const to = keys.indexOf(targetKey);
293
308
  if (from < 0 || to < 0) return;
294
309
  keys.splice(to, 0, keys.splice(from, 1)[0]);
295
- persistOrder(keys);
310
+ persistTaskOrder(keys);
296
311
  };
297
312
 
298
- const togglePicked = (key) => setPickedRows((prev) => {
313
+ const togglePicked = (key) => setPickedTasks((prev) => {
299
314
  const next = new Set(prev);
300
315
  if (next.has(key)) next.delete(key); else next.add(key);
301
316
  return next;
302
317
  });
303
318
 
304
- const deleteRows = async (rows) => {
305
- if (rows.length === 0) return;
306
- if (!window.confirm(`确认删除 ${rows.length} 条评测成绩?将同时删除对应的模型答案记录,不可恢复。`)) return;
319
+ // 删除一个任务行 = 删除该任务下所有模型的判分与答案记录
320
+ const deleteTasks = async (keys) => {
321
+ if (!results || keys.length === 0) return;
322
+ const serverRows = [];
323
+ for (const key of keys) {
324
+ const [category, task] = key.split("/");
325
+ for (const row of results.rows) {
326
+ if (row.category === category && row.task === task) {
327
+ serverRows.push({ model: row.model, category, task });
328
+ }
329
+ }
330
+ }
331
+ if (serverRows.length === 0) return;
332
+ if (!window.confirm(`确认删除所选 ${keys.length} 个任务的全部评测成绩(共 ${serverRows.length} 条模型记录,含答案)?不可恢复。`)) return;
307
333
  await api("/delete", {
308
334
  method: "POST",
309
335
  headers: { "content-type": "application/json" },
310
- body: JSON.stringify({ rows: rows.map((r) => ({ model: r.model, category: r.category, task: r.task })) }),
336
+ body: JSON.stringify({ rows: serverRows }),
311
337
  });
312
- setPickedRows(new Set());
313
- persistOrder([...order].filter((key) => !rows.some((r) => rowKey(r) === key)));
338
+ setPickedTasks(new Set());
339
+ persistTaskOrder(taskOrder.filter((key) => !keys.includes(key)));
314
340
  await loadResults();
315
341
  };
316
342
 
@@ -405,32 +431,29 @@ window.__ModuleLoader__.load({
405
431
  ),
406
432
  h("pre", { className: c("log"), ref: logRef }, log || "(暂无输出)"),
407
433
  ),
408
- sortedRows.length > 0 && h("div", { className: c("card") },
434
+ sortedTasks.length > 0 && h("div", { className: c("card") },
409
435
  h("div", { className: c("row") },
410
- h("span", { className: c("label") }, "评测成绩(分数 = 平均分 ×100;行首可拖动排序,顺序保存在本机)"),
436
+ h("span", { className: c("label") }, "评测成绩(分数 = 平均分 ×100,括号内为 已判/总题数;行首可拖动排序,顺序保存在本机)"),
411
437
  h("button", {
412
438
  className: c("btn") + " " + c("btnDanger"),
413
- disabled: pickedRows.size === 0,
414
- onClick: () => deleteRows(sortedRows.filter((r) => pickedRows.has(rowKey(r)))),
415
- }, `删除所选(${pickedRows.size})`),
439
+ disabled: pickedTasks.size === 0,
440
+ onClick: () => deleteTasks([...pickedTasks]),
441
+ }, `删除所选(${pickedTasks.size})`),
416
442
  ),
417
443
  h("table", { className: c("table") },
418
444
  h("thead", null, h("tr", null,
419
- h("th", { style: { width: "30px" } }, "⠿"),
445
+ h("th", { style: { width: "26px" } }, "⠿"),
420
446
  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"),
424
- h("th", null, "model"),
425
- h("th", null, "category"),
447
+ h("input", { type: "checkbox", checked: pickedTasks.size > 0 && pickedTasks.size === sortedTasks.length,
448
+ onChange: (event) => setPickedTasks(event.target.checked ? new Set(sortedTasks.map(([key]) => key)) : new Set()) })),
426
449
  h("th", null, "task"),
427
- h("th", null, "score"),
428
- h("th", null, "操作"),
450
+ matrix.models.map((model) => h("th", { key: model }, model)),
451
+ h("th", null, "删除"),
452
+ h("th", null, "time"),
429
453
  )),
430
454
  h("tbody", null,
431
- sortedRows.map((row) => {
432
- const key = rowKey(row);
433
- const checked = pickedRows.has(key);
455
+ sortedTasks.map(([key, entry]) => {
456
+ const checked = pickedTasks.has(key);
434
457
  return h("tr", {
435
458
  key,
436
459
  draggable: true,
@@ -440,15 +463,17 @@ window.__ModuleLoader__.load({
440
463
  },
441
464
  h("td", { className: c("drag"), title: "拖动排序" }, "⠿"),
442
465
  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})`),
466
+ h("td", null, h("span", { title: `${entry.category} / ${entry.task}` }, `${entry.category} / ${entry.task}`)),
467
+ matrix.models.map((model) => {
468
+ const row = entry.cells.get(model);
469
+ return h("td", { key: model, className: c("score") },
470
+ row ? `${row.score.toFixed(1)} (${row.judged}/${row.total})` : "—");
471
+ }),
448
472
  h("td", null, h("button", {
449
- className: c("btnGhost") + " " + c("btn"), title: "删除此条成绩",
450
- onClick: () => deleteRows([row]),
473
+ className: c("btnGhost") + " " + c("btn"), title: "删除该任务下所有模型的评测成绩",
474
+ onClick: () => deleteTasks([key]),
451
475
  }, "删除")),
476
+ h("td", null, fmtTime(entry.startTime === Infinity ? null : entry.startTime)),
452
477
  );
453
478
  })),
454
479
  ),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-livebench-panel",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
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",