dsh-livebench-panel 0.1.17 → 0.1.18
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 +13 -12
- package/package.json +1 -1
- package/lib/lbv_new_body.js +0 -522
package/lib/client.js
CHANGED
|
@@ -13,6 +13,7 @@ window.__ModuleLoader__.load({
|
|
|
13
13
|
.dlb_head h2{margin:0;font-size:16px;font-weight:600}
|
|
14
14
|
.dlb_sub{color:var(--dsw-alias-label-tertiary);font-size:12px}
|
|
15
15
|
.dlb_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:10px;padding:14px;display:flex;flex-direction:column;gap:12px}
|
|
16
|
+
.dlb_fields{display:flex;flex-direction:column;gap:12px}
|
|
16
17
|
.dlb_grid{display:grid;grid-template-columns:repeat(12,1fr);gap:10px}
|
|
17
18
|
@media (max-width:860px){.dlb_grid>.dlb_field{grid-column:1/-1 !important}}
|
|
18
19
|
.dlb_field{display:flex;flex-direction:column;gap:4px;min-width:0}
|
|
@@ -52,7 +53,7 @@ window.__ModuleLoader__.load({
|
|
|
52
53
|
.dlb_chip:disabled{opacity:.4;cursor:not-allowed}
|
|
53
54
|
.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)}
|
|
54
55
|
.dlb_btnRow{display:flex;gap:10px;justify-content:center;margin-top:2px}
|
|
55
|
-
.dlb_btnBar{
|
|
56
|
+
.dlb_btnBar{min-width:190px;min-height:40px;font-weight:600;text-align:center}
|
|
56
57
|
@media (max-width:700px){.dlb_btnRow{flex-wrap:wrap}.dlb_btnBar{max-width:none}}
|
|
57
58
|
.dlb_drag{cursor:grab;color:var(--dsw-alias-label-tertiary);text-align:center;user-select:none}`;
|
|
58
59
|
const tagId = "dsh-livebench-panel/panel.css";
|
|
@@ -397,8 +398,8 @@ window.__ModuleLoader__.load({
|
|
|
397
398
|
),
|
|
398
399
|
configError !== null && h("p", { className: c("error") }, `加载配置失败:${configError}`),
|
|
399
400
|
h("div", { className: c("card") },
|
|
400
|
-
h("div", { className: c("
|
|
401
|
-
h("div", { className: c("field"), ref: modelDdRef
|
|
401
|
+
h("div", { className: c("fields") },
|
|
402
|
+
h("div", { className: c("field"), ref: modelDdRef },
|
|
402
403
|
h("label", { className: c("label") }, "模型(harness 全部模型,可多选;勾选后可在行内单独设定推理强度)"),
|
|
403
404
|
h("button", {
|
|
404
405
|
type: "button", className: c("select"), style: { textAlign: "left", width: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
@@ -439,23 +440,23 @@ window.__ModuleLoader__.load({
|
|
|
439
440
|
m.efforts.map((e) => h("option", { key: e, value: e }, e === "off" ? "off(关闭思考)" : e)))));
|
|
440
441
|
}))),
|
|
441
442
|
),
|
|
442
|
-
h("div", { className: c("field")
|
|
443
|
+
h("div", { className: c("field") },
|
|
443
444
|
h("label", { className: c("label") }, "题集 release"),
|
|
444
445
|
h("select", { className: c("select"), value: sel.release, onChange: setField("release") },
|
|
445
446
|
(config?.releases ?? ["2024-11-25"]).map((r) => h("option", { key: r, value: r }, r))),
|
|
446
447
|
),
|
|
447
|
-
|
|
448
|
+
),
|
|
449
|
+
h("div", { className: c("field") },
|
|
450
|
+
h("label", { className: c("label") }, "max-tokens(默认 32000)"),
|
|
451
|
+
h("input", { className: c("input"), type: "number", min: 256, max: 32768, value: sel.maxTokens, onChange: setField("maxTokens") }),
|
|
452
|
+
),
|
|
453
|
+
h("div", { className: c("field") },
|
|
448
454
|
h("label", { className: c("label") }, "题目序号范围(可选)"),
|
|
449
455
|
h("div", { className: c("row") },
|
|
450
456
|
h("input", { className: c("input"), type: "number", min: 0, placeholder: "起", value: sel.begin, onChange: setField("begin"), style: { width: "50%" } }),
|
|
451
457
|
h("input", { className: c("input"), type: "number", min: 0, placeholder: "止", value: sel.end, onChange: setField("end"), style: { width: "50%" } }),
|
|
452
458
|
),
|
|
453
|
-
),
|
|
454
|
-
h("div", { className: c("field"), style: { gridColumn: "span 2" } },
|
|
455
|
-
h("label", { className: c("label") }, "max-tokens(默认 32000)"),
|
|
456
|
-
h("input", { className: c("input"), type: "number", min: 256, max: 32768, value: sel.maxTokens, onChange: setField("maxTokens") }),
|
|
457
|
-
),
|
|
458
|
-
h("div", { className: c("field"), style: { gridColumn: "1 / -1" } },
|
|
459
|
+
h("div", { className: c("field") },
|
|
459
460
|
h("label", { className: c("label") }, "分类(可多选,不选 = 全部分类;括号内为该 release 下有效题数)"),
|
|
460
461
|
h("div", { className: c("chips") },
|
|
461
462
|
categories.map((cat) => {
|
|
@@ -467,7 +468,7 @@ window.__ModuleLoader__.load({
|
|
|
467
468
|
}, `${cat}(${n})`);
|
|
468
469
|
})),
|
|
469
470
|
),
|
|
470
|
-
sel.cats.length > 0 && h("div", { className: c("field")
|
|
471
|
+
sel.cats.length > 0 && h("div", { className: c("field") },
|
|
471
472
|
h("label", { className: c("label") }, "任务(可多选,不选 = 所选分类的全部任务)"),
|
|
472
473
|
h("div", { className: c("chips") },
|
|
473
474
|
taskChips.map((key) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-livebench-panel",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
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",
|
package/lib/lbv_new_body.js
DELETED
|
@@ -1,522 +0,0 @@
|
|
|
1
|
-
const [config, setConfig] = useState(null);
|
|
2
|
-
const [configError, setConfigError] = useState(null);
|
|
3
|
-
const [sel, setSel] = useState({ models: [], efforts: {}, cats: [], tasks: [], release: "2024-11-25", begin: "", end: "", maxTokens: "32000" });
|
|
4
|
-
const [modelDdOpen, setModelDdOpen] = useState(false);
|
|
5
|
-
const [busy, setBusy] = useState(false);
|
|
6
|
-
const [running, setRunning] = useState(false);
|
|
7
|
-
const [runsList, setRunsList] = useState([]);
|
|
8
|
-
const [startError, setStartError] = useState(null);
|
|
9
|
-
const [homeInput, setHomeInput] = useState("");
|
|
10
|
-
const [homeBusy, setHomeBusy] = useState(false);
|
|
11
|
-
const [results, setResults] = useState(null);
|
|
12
|
-
const [pickedModels, setPickedModels] = useState(() => new Set());
|
|
13
|
-
const [modelOrder, setModelOrder] = useState(() => {
|
|
14
|
-
try { return JSON.parse(localStorage.getItem("dlb_model_order_v1")) ?? []; } catch { return []; }
|
|
15
|
-
});
|
|
16
|
-
const dragKey = useRef(null);
|
|
17
|
-
|
|
18
|
-
// 点击面板外部时关闭模型下拉
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
if (!modelDdOpen) return undefined;
|
|
21
|
-
const onDocDown = (event) => {
|
|
22
|
-
if (modelDdRef.current && !modelDdRef.current.contains(event.target)) setModelDdOpen(false);
|
|
23
|
-
};
|
|
24
|
-
document.addEventListener("mousedown", onDocDown);
|
|
25
|
-
return () => document.removeEventListener("mousedown", onDocDown);
|
|
26
|
-
}, [modelDdOpen]);
|
|
27
|
-
|
|
28
|
-
const loadConfig = useCallback(async () => {
|
|
29
|
-
const payload = await api("/config");
|
|
30
|
-
if (payload.ok) {
|
|
31
|
-
setConfig(payload);
|
|
32
|
-
setConfigError(null);
|
|
33
|
-
setSel((prev) => {
|
|
34
|
-
const next = { ...prev, release: payload.releases.includes(prev.release) ? prev.release : "2024-11-25" };
|
|
35
|
-
next.models = prev.models.filter((value) => {
|
|
36
|
-
const pid = value.split("::")[0];
|
|
37
|
-
return payload.providers.some((p) => p.id === pid);
|
|
38
|
-
});
|
|
39
|
-
return next;
|
|
40
|
-
});
|
|
41
|
-
} else {
|
|
42
|
-
setConfigError(payload.error ?? "config unavailable");
|
|
43
|
-
}
|
|
44
|
-
}, []);
|
|
45
|
-
|
|
46
|
-
const loadResults = useCallback(async () => {
|
|
47
|
-
const payload = await api("/results");
|
|
48
|
-
if (payload.ok) setResults(payload);
|
|
49
|
-
}, []);
|
|
50
|
-
|
|
51
|
-
const refreshStatus = useCallback(async () => {
|
|
52
|
-
const payload = await api("/status");
|
|
53
|
-
if (payload.ok) {
|
|
54
|
-
setRunsList(payload.runs ?? []);
|
|
55
|
-
setRunning(payload.running === true);
|
|
56
|
-
return payload.running === true;
|
|
57
|
-
}
|
|
58
|
-
return false;
|
|
59
|
-
}, []);
|
|
60
|
-
|
|
61
|
-
useEffect(() => {
|
|
62
|
-
loadConfig();
|
|
63
|
-
loadResults();
|
|
64
|
-
refreshStatus();
|
|
65
|
-
}, [loadConfig, loadResults, refreshStatus]);
|
|
66
|
-
|
|
67
|
-
// 轮询评测状态;从运行中转为结束后自动刷新成绩
|
|
68
|
-
const wasRunning = useRef(false);
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
const timer = setInterval(async () => {
|
|
71
|
-
const active = await refreshStatus();
|
|
72
|
-
if (wasRunning.current && !active) loadResults();
|
|
73
|
-
wasRunning.current = active;
|
|
74
|
-
}, 2500);
|
|
75
|
-
return () => clearInterval(timer);
|
|
76
|
-
}, [refreshStatus, loadResults]);
|
|
77
|
-
|
|
78
|
-
const categories = useMemo(() => (config ? Object.keys(config.tasks) : []), [config]);
|
|
79
|
-
// 有效题数:LiveBench 丢弃「发布晚于所选 release」与「在所选 release 前已退役」的题目
|
|
80
|
-
const countFor = useCallback((category, task) => {
|
|
81
|
-
if (!config || category.length === 0) return null;
|
|
82
|
-
const meta = (config.tasks[category] ?? {})[task];
|
|
83
|
-
if (!meta || !Array.isArray(meta.buckets)) return null;
|
|
84
|
-
const option = sel.release;
|
|
85
|
-
return meta.buckets.reduce((sum, b) => {
|
|
86
|
-
const releasedOk = b.r !== "" && b.r <= option;
|
|
87
|
-
const notRemoved = b.rm === "" || b.rm > option;
|
|
88
|
-
return sum + (releasedOk && notRemoved ? b.n : 0);
|
|
89
|
-
}, 0);
|
|
90
|
-
}, [config, sel.release]);
|
|
91
|
-
const catCount = useCallback((category) => {
|
|
92
|
-
if (!config) return 0;
|
|
93
|
-
return Object.keys(config.tasks[category] ?? {}).reduce((sum, task) => sum + (countFor(category, task) ?? 0), 0);
|
|
94
|
-
}, [config, countFor]);
|
|
95
|
-
const taskChips = useMemo(() => {
|
|
96
|
-
const cats = sel.cats.length > 0 ? sel.cats : categories;
|
|
97
|
-
return cats.flatMap((cat) => Object.keys(config?.tasks[cat] ?? {}).map((task) => `${cat}/${task}`));
|
|
98
|
-
}, [sel.cats, categories, config]);
|
|
99
|
-
const toggleList = (key, value) => setSel((prev) => {
|
|
100
|
-
const list = prev[key];
|
|
101
|
-
const next = list.includes(value) ? list.filter((v) => v !== value) : [...list, value];
|
|
102
|
-
return { ...prev, [key]: next };
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// 已选模型解析(provider::model)
|
|
106
|
-
const selectedModelEntries = useMemo(() => sel.models.map((value) => {
|
|
107
|
-
const sep = value.indexOf("::");
|
|
108
|
-
const pid = value.slice(0, sep);
|
|
109
|
-
const mid = value.slice(sep + 2);
|
|
110
|
-
const provider = config?.providers.find((p) => p.id === pid) ?? null;
|
|
111
|
-
const model = provider?.models.find((m) => m.id === mid) ?? null;
|
|
112
|
-
return { providerId: pid, modelId: mid, modelValue: value, efforts: model?.efforts ?? [] };
|
|
113
|
-
}), [sel.models, config]);
|
|
114
|
-
|
|
115
|
-
const onStart = async () => {
|
|
116
|
-
setStartError(null);
|
|
117
|
-
if (selectedModelEntries.length === 0) {
|
|
118
|
-
setStartError("请先在模型下拉中至少勾选一个模型。");
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
const benchNames = [];
|
|
122
|
-
if (sel.cats.length === 0) {
|
|
123
|
-
benchNames.push("live_bench");
|
|
124
|
-
} else if (sel.tasks.length === 0) {
|
|
125
|
-
for (const cat of sel.cats) benchNames.push(`live_bench/${cat}`);
|
|
126
|
-
} else {
|
|
127
|
-
for (const t of sel.tasks) benchNames.push(`live_bench/${t}`);
|
|
128
|
-
}
|
|
129
|
-
const zeroTask = sel.tasks.find((t) => countFor(t.split("/")[0], t.split("/")[1]) === 0);
|
|
130
|
-
if (zeroTask) {
|
|
131
|
-
setStartError(`任务 ${zeroTask.split("/")[1]} 在 release ${sel.release} 下没有可用题目(该批题目已退役),请取消勾选或换 release。`);
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
setBusy(true);
|
|
135
|
-
try {
|
|
136
|
-
// 每个模型并发启动一个评测(服务端并发上限 6)
|
|
137
|
-
const failures = [];
|
|
138
|
-
const launches = selectedModelEntries.map(async (entry) => {
|
|
139
|
-
const payload = await api("/start", {
|
|
140
|
-
method: "POST",
|
|
141
|
-
headers: { "content-type": "application/json" },
|
|
142
|
-
body: JSON.stringify({
|
|
143
|
-
provider: entry.providerId,
|
|
144
|
-
model: entry.modelId,
|
|
145
|
-
reasoningEffort: sel.efforts[entry.modelValue] ?? "default",
|
|
146
|
-
benchNames,
|
|
147
|
-
release: sel.release,
|
|
148
|
-
begin: sel.begin,
|
|
149
|
-
end: sel.end,
|
|
150
|
-
maxTokens: sel.maxTokens,
|
|
151
|
-
}),
|
|
152
|
-
});
|
|
153
|
-
if (!payload.ok) failures.push(`${entry.modelId}: ${payload.error ?? "启动失败"}`);
|
|
154
|
-
});
|
|
155
|
-
await Promise.all(launches);
|
|
156
|
-
if (failures.length > 0) setStartError(failures.join(";"));
|
|
157
|
-
await refreshStatus();
|
|
158
|
-
} catch (error) {
|
|
159
|
-
setStartError(String(error.message ?? error));
|
|
160
|
-
} finally {
|
|
161
|
-
setBusy(false);
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
const onStop = async () => {
|
|
166
|
-
await api("/stop", { method: "POST" });
|
|
167
|
-
await refreshStatus();
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
const saveHome = async () => {
|
|
171
|
-
setHomeBusy(true);
|
|
172
|
-
setStartError(null);
|
|
173
|
-
try {
|
|
174
|
-
const payload = await api("/home", {
|
|
175
|
-
method: "POST",
|
|
176
|
-
headers: { "content-type": "application/json" },
|
|
177
|
-
body: JSON.stringify({ path: homeInput }),
|
|
178
|
-
});
|
|
179
|
-
if (!payload.ok) setStartError(payload.error ?? "设置失败");
|
|
180
|
-
else { setHomeInput(""); await loadConfig(); }
|
|
181
|
-
} catch (error) {
|
|
182
|
-
setStartError(String(error.message ?? error));
|
|
183
|
-
} finally {
|
|
184
|
-
setHomeBusy(false);
|
|
185
|
-
}
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const setField = (key) => (event) => {
|
|
189
|
-
const value = event.target.value;
|
|
190
|
-
setSel((prev) => ({ ...prev, [key]: value }));
|
|
191
|
-
};
|
|
192
|
-
const toggleModel = (value) => setSel((prev) => {
|
|
193
|
-
const set = new Set(prev.models);
|
|
194
|
-
const nextEfforts = { ...prev.efforts };
|
|
195
|
-
if (set.has(value)) { set.delete(value); delete nextEfforts[value]; }
|
|
196
|
-
else set.add(value);
|
|
197
|
-
return { ...prev, models: [...set], efforts: nextEfforts };
|
|
198
|
-
});
|
|
199
|
-
const setModelEffort = (value) => (event) => {
|
|
200
|
-
const v = event.target.value;
|
|
201
|
-
setSel((prev) => ({ ...prev, efforts: { ...prev.efforts, [value]: v } }));
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
// ---- 成绩矩阵:model 为行标识、category/task 为列标识 ----
|
|
205
|
-
// time 列 = 该模型评测开始时间(行内最早判分时间)
|
|
206
|
-
const taskKey = (category, task) => `${category}/${task}`;
|
|
207
|
-
const matrix = useMemo(() => {
|
|
208
|
-
if (!results) return null;
|
|
209
|
-
const models = [...new Set(results.rows.map((r) => r.model))].sort();
|
|
210
|
-
const tasks = [...new Set(results.rows.map((r) => taskKey(r.category, r.task)))].sort();
|
|
211
|
-
const cells = new Map();
|
|
212
|
-
const startTimes = new Map();
|
|
213
|
-
for (const row of results.rows) {
|
|
214
|
-
cells.set(`${row.model}__@__${taskKey(row.category, row.task)}`, row);
|
|
215
|
-
const prev = startTimes.get(row.model);
|
|
216
|
-
if (row.time && (prev === undefined || row.time < prev)) startTimes.set(row.model, row.time);
|
|
217
|
-
}
|
|
218
|
-
return { models, tasks, cells, startTimes };
|
|
219
|
-
}, [results]);
|
|
220
|
-
const cellOf = (model, key) => matrix.cells.get(`${model}__@__${key}`);
|
|
221
|
-
const sortedModels = useMemo(() => {
|
|
222
|
-
if (!matrix) return [];
|
|
223
|
-
const indexOf = new Map(modelOrder.map((key, index) => [key, index]));
|
|
224
|
-
return [...matrix.models].sort((a, b) => {
|
|
225
|
-
const ia = indexOf.has(a) ? indexOf.get(a) : Number.MAX_SAFE_INTEGER;
|
|
226
|
-
const ib = indexOf.has(b) ? indexOf.get(b) : Number.MAX_SAFE_INTEGER;
|
|
227
|
-
if (ia !== ib) return ia - ib;
|
|
228
|
-
return a.localeCompare(b);
|
|
229
|
-
});
|
|
230
|
-
}, [matrix, modelOrder]);
|
|
231
|
-
|
|
232
|
-
const persistModelOrder = (keys) => {
|
|
233
|
-
setModelOrder(keys);
|
|
234
|
-
try { localStorage.setItem("dlb_model_order_v1", JSON.stringify(keys)); } catch { /* ignore */ }
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
const onRowDrop = (targetModel) => {
|
|
238
|
-
const sourceModel = dragKey.current;
|
|
239
|
-
dragKey.current = null;
|
|
240
|
-
if (!sourceModel || sourceModel === targetModel) return;
|
|
241
|
-
const keys = sortedModels.slice();
|
|
242
|
-
const from = keys.indexOf(sourceModel);
|
|
243
|
-
const to = keys.indexOf(targetModel);
|
|
244
|
-
if (from < 0 || to < 0) return;
|
|
245
|
-
keys.splice(to, 0, keys.splice(from, 1)[0]);
|
|
246
|
-
persistModelOrder(keys);
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
const togglePicked = (model) => setPickedModels((prev) => {
|
|
250
|
-
const next = new Set(prev);
|
|
251
|
-
if (next.has(model)) next.delete(model); else next.add(model);
|
|
252
|
-
return next;
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
// 删除一个模型行 = 删除该模型在所有任务下的判分与答案记录
|
|
256
|
-
const deleteModels = async (modelsToDelete) => {
|
|
257
|
-
if (!results || modelsToDelete.length === 0) return;
|
|
258
|
-
const serverRows = [];
|
|
259
|
-
for (const row of results.rows) {
|
|
260
|
-
if (modelsToDelete.includes(row.model)) {
|
|
261
|
-
serverRows.push({ model: row.model, category: row.category, task: row.task });
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
if (serverRows.length === 0) return;
|
|
265
|
-
if (!window.confirm(`确认删除所选 ${modelsToDelete.length} 个模型的全部评测成绩(共 ${serverRows.length} 条记录,含答案)?不可恢复。`)) return;
|
|
266
|
-
await api("/delete", {
|
|
267
|
-
method: "POST",
|
|
268
|
-
headers: { "content-type": "application/json" },
|
|
269
|
-
body: JSON.stringify({ rows: serverRows }),
|
|
270
|
-
});
|
|
271
|
-
setPickedModels(new Set());
|
|
272
|
-
persistModelOrder(modelOrder.filter((model) => !modelsToDelete.includes(model)));
|
|
273
|
-
await loadResults();
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
const fmtTime = (t) => {
|
|
277
|
-
if (!t) return "—";
|
|
278
|
-
const dt = new Date(t * 1000);
|
|
279
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
280
|
-
return `${dt.getFullYear()}-${pad(dt.getMonth() + 1)}-${pad(dt.getDate())} ${pad(dt.getHours())}:${pad(dt.getMinutes())}`;
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
return h("div", { className: c("root") },
|
|
284
|
-
h("div", { className: c("head") },
|
|
285
|
-
h("h2", null, "LiveBench"),
|
|
286
|
-
h("span", { className: c("sub") }, config?.root ? `LiveBench 评测面板 · ${config.root}` : "LiveBench 评测面板"),
|
|
287
|
-
h("span", { className: c("badge"), "data-ok": config?.available ? "1" : "0" },
|
|
288
|
-
config === null ? "检测中…" : config.available ? "LiveBench 就绪" : "未找到 LiveBench"),
|
|
289
|
-
),
|
|
290
|
-
h("div", { className: c("row") },
|
|
291
|
-
h("span", { className: c("label") }, "LiveBench 路径"),
|
|
292
|
-
h("input", {
|
|
293
|
-
className: c("input"), style: { flex: "1", minWidth: "240px" },
|
|
294
|
-
placeholder: config?.root ?? "选择或输入 LiveBench 目录",
|
|
295
|
-
value: homeInput, onChange: (event) => setHomeInput(event.target.value),
|
|
296
|
-
}),
|
|
297
|
-
h("button", { className: c("btnGhost") + " " + c("btn"), disabled: homeBusy || homeInput.trim().length === 0, onClick: saveHome },
|
|
298
|
-
homeBusy ? "保存中…" : "设置路径"),
|
|
299
|
-
h("span", { className: c("hint") }, "需含 livebench\\run_livebench.py 与 .venv;也可用环境变量 DSH_LIVEBENCH_HOME"),
|
|
300
|
-
),
|
|
301
|
-
configError !== null && h("p", { className: c("error") }, `加载配置失败:${configError}`),
|
|
302
|
-
h("div", { className: c("card") },
|
|
303
|
-
h("div", { className: c("grid") },
|
|
304
|
-
h("div", { className: c("field"), ref: modelDdRef, style: { gridColumn: "span 6" } },
|
|
305
|
-
h("label", { className: c("label") }, "模型(harness 全部模型,可多选;勾选后可在行内单独设定推理强度)"),
|
|
306
|
-
h("button", {
|
|
307
|
-
type: "button", className: c("select"), style: { textAlign: "left", width: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
308
|
-
onClick: () => setModelDdOpen((v) => !v),
|
|
309
|
-
}, sel.models.length === 0
|
|
310
|
-
? "点击选择模型…"
|
|
311
|
-
: sel.models.length === 1
|
|
312
|
-
? sel.models[0].split("::")[1]
|
|
313
|
-
: `已选 ${sel.models.length} 个模型`),
|
|
314
|
-
modelDdOpen && h("div", {
|
|
315
|
-
style: {
|
|
316
|
-
marginTop: "6px", maxHeight: "340px", overflowY: "auto", border: "1px solid var(--dsw-alias-border-l2)",
|
|
317
|
-
borderRadius: "8px", background: "var(--dsw-alias-bg-layer-1)", padding: "6px",
|
|
318
|
-
},
|
|
319
|
-
},
|
|
320
|
-
(config?.providers ?? []).map((p) => h("div", { key: p.id, style: { marginBottom: "4px" } },
|
|
321
|
-
h("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary)", padding: "3px 4px" } },
|
|
322
|
-
p.name + (p.routable ? "" : " ·未接LiveBench")),
|
|
323
|
-
p.models.map((m) => {
|
|
324
|
-
const value = p.id + "::" + m.id;
|
|
325
|
-
const checked = sel.models.includes(value);
|
|
326
|
-
return h("div", { key: value, style: { borderBottom: "1px solid var(--dsw-alias-border-l2)", padding: "3px 4px" } },
|
|
327
|
-
h("div", {
|
|
328
|
-
style: { display: "flex", alignItems: "center", gap: "8px", cursor: "pointer" },
|
|
329
|
-
onClick: () => toggleModel(value),
|
|
330
|
-
},
|
|
331
|
-
h("input", { type: "checkbox", checked, readOnly: true, style: { cursor: "pointer" } }),
|
|
332
|
-
h("span", { style: { fontSize: "12.5px", flex: 1 } }, m.name)),
|
|
333
|
-
checked && m.efforts.length > 0 && h("div", { style: { display: "flex", alignItems: "center", gap: "6px", padding: "4px 0 2px 26px" } },
|
|
334
|
-
h("span", { style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary)", whiteSpace: "nowrap" } }, "推理强度"),
|
|
335
|
-
h("select", {
|
|
336
|
-
className: c("select"), style: { height: "28px", fontSize: "12px", flex: 1 },
|
|
337
|
-
value: sel.efforts[value] ?? "default",
|
|
338
|
-
onClick: (event) => event.stopPropagation(),
|
|
339
|
-
onChange: setModelEffort(value),
|
|
340
|
-
},
|
|
341
|
-
h("option", { value: "default" }, "(模型默认)"),
|
|
342
|
-
m.efforts.map((e) => h("option", { key: e, value: e }, e === "off" ? "off(关闭思考)" : e))));
|
|
343
|
-
}))),
|
|
344
|
-
),
|
|
345
|
-
h("div", { className: c("field"), style: { gridColumn: "span 2" } },
|
|
346
|
-
h("label", { className: c("label") }, "题集 release"),
|
|
347
|
-
h("select", { className: c("select"), value: sel.release, onChange: setField("release") },
|
|
348
|
-
(config?.releases ?? ["2024-11-25"]).map((r) => h("option", { key: r, value: r }, r))),
|
|
349
|
-
),
|
|
350
|
-
h("div", { className: c("field"), style: { gridColumn: "span 2" } },
|
|
351
|
-
h("label", { className: c("label") }, "题目序号范围(可选)"),
|
|
352
|
-
h("div", { className: c("row") },
|
|
353
|
-
h("input", { className: c("input"), type: "number", min: 0, placeholder: "起", value: sel.begin, onChange: setField("begin"), style: { width: "50%" } }),
|
|
354
|
-
h("input", { className: c("input"), type: "number", min: 0, placeholder: "止", value: sel.end, onChange: setField("end"), style: { width: "50%" } }),
|
|
355
|
-
),
|
|
356
|
-
),
|
|
357
|
-
h("div", { className: c("field"), style: { gridColumn: "span 2" } },
|
|
358
|
-
h("label", { className: c("label") }, "max-tokens(默认 32000)"),
|
|
359
|
-
h("input", { className: c("input"), type: "number", min: 256, max: 32768, value: sel.maxTokens, onChange: setField("maxTokens") }),
|
|
360
|
-
),
|
|
361
|
-
h("div", { className: c("field"), style: { gridColumn: "1 / -1" } },
|
|
362
|
-
h("label", { className: c("label") }, "分类(可多选,不选 = 全部分类;括号内为该 release 下有效题数)"),
|
|
363
|
-
h("div", { className: c("chips") },
|
|
364
|
-
categories.map((cat) => {
|
|
365
|
-
const n = catCount(cat);
|
|
366
|
-
const active = sel.cats.includes(cat);
|
|
367
|
-
return h("button", {
|
|
368
|
-
key: cat, type: "button", className: c("chip") + (active ? " " + c("chipOn") : ""),
|
|
369
|
-
onClick: () => toggleList("cats", cat), disabled: n === 0,
|
|
370
|
-
}, `${cat}(${n})`);
|
|
371
|
-
})),
|
|
372
|
-
),
|
|
373
|
-
sel.cats.length > 0 && h("div", { className: c("field"), style: { gridColumn: "1 / -1" } },
|
|
374
|
-
h("label", { className: c("label") }, "任务(可多选,不选 = 所选分类的全部任务)"),
|
|
375
|
-
h("div", { className: c("chips") },
|
|
376
|
-
taskChips.map((key) => {
|
|
377
|
-
const [cat, task] = key.split("/");
|
|
378
|
-
const n = countFor(cat, task) ?? 0;
|
|
379
|
-
const active = sel.tasks.includes(key);
|
|
380
|
-
return h("button", {
|
|
381
|
-
key, type: "button", className: c("chip") + (active ? " " + c("chipOn") : ""),
|
|
382
|
-
onClick: () => toggleList("tasks", key), disabled: n === 0,
|
|
383
|
-
title: n === 0 ? "该任务在此 release 下已无可用题目" : `${n} 题有效`,
|
|
384
|
-
}, `${task}(${n})`);
|
|
385
|
-
})),
|
|
386
|
-
),
|
|
387
|
-
),
|
|
388
|
-
h("div", { className: c("btnRow") },
|
|
389
|
-
h("button", {
|
|
390
|
-
className: c("btn") + " " + c("btnBar"), onClick: onStart,
|
|
391
|
-
disabled: busy || !config?.available || sel.models.length === 0,
|
|
392
|
-
}, `开始评测(${sel.models.length} 个模型并发)`),
|
|
393
|
-
running && h("button", { className: c("btn") + " " + c("btnDanger") + " " + c("btnBar"), onClick: onStop }, "停止全部"),
|
|
394
|
-
h("button", { className: c("btnGhost") + " " + c("btn") + " " + c("btnBar"), onClick: () => { loadConfig(); loadResults(); } }, "刷新"),
|
|
395
|
-
),
|
|
396
|
-
startError !== null && h("p", { className: c("error") }, startError),
|
|
397
|
-
),
|
|
398
|
-
runsList.length > 0 && h("div", { className: c("card") },
|
|
399
|
-
h("div", { className: c("row") },
|
|
400
|
-
h("span", { className: c("badge"), "data-ok": running ? "1" : "0" },
|
|
401
|
-
running ? "有评测运行中" : "无运行中的评测"),
|
|
402
|
-
),
|
|
403
|
-
runsList.map((r) => h("details", { key: r.runId, open: r.running },
|
|
404
|
-
h("summary", { style: { cursor: "pointer", fontSize: "12px", color: "var(--dsw-alias-label-secondary)" } },
|
|
405
|
-
h("span", { className: c("badge"), "data-ok": r.running ? "1" : "0" }, r.running ? "运行中" : r.exitCode === 0 ? "完成" : `退出(${r.exitCode})`),
|
|
406
|
-
" ",
|
|
407
|
-
r.displayName),
|
|
408
|
-
h("pre", { className: c("log") }, r.log || "(暂无输出)"),
|
|
409
|
-
))),
|
|
410
|
-
),
|
|
411
|
-
sortedModels.length > 0 && h("div", { className: c("card") },
|
|
412
|
-
h("div", { className: c("row") },
|
|
413
|
-
h("span", { className: c("label") }, "评测成绩(model 为行;分数 = 平均分 ×100;行首可拖动排序,顺序保存在本机)"),
|
|
414
|
-
h("button", {
|
|
415
|
-
className: c("btn") + " " + c("btnDanger"),
|
|
416
|
-
disabled: pickedModels.size === 0,
|
|
417
|
-
onClick: () => deleteModels([...pickedModels]),
|
|
418
|
-
}, `删除所选(${pickedModels.size})`),
|
|
419
|
-
),
|
|
420
|
-
h("table", { className: c("table") },
|
|
421
|
-
h("thead", null, h("tr", null,
|
|
422
|
-
h("th", { style: { width: "26px" } }, "⠿"),
|
|
423
|
-
h("th", { style: { width: "30px" } },
|
|
424
|
-
h("input", { type: "checkbox", checked: pickedModels.size > 0 && pickedModels.size === sortedModels.length,
|
|
425
|
-
onChange: (event) => setPickedModels(event.target.checked ? new Set(sortedModels) : new Set()) })),
|
|
426
|
-
h("th", null, "model"),
|
|
427
|
-
matrix.tasks.map((taskKeyCol) => {
|
|
428
|
-
const [cat, task] = taskKeyCol.split("/");
|
|
429
|
-
return h("th", { key: taskKeyCol, title: `${cat} / ${task}` }, task);
|
|
430
|
-
}),
|
|
431
|
-
h("th", null, "删除"),
|
|
432
|
-
h("th", null, "time"),
|
|
433
|
-
)),
|
|
434
|
-
h("tbody", null,
|
|
435
|
-
sortedModels.map((model) => {
|
|
436
|
-
const checked = pickedModels.has(model);
|
|
437
|
-
const startTime = matrix.startTimes.get(model);
|
|
438
|
-
return h("tr", {
|
|
439
|
-
key: model,
|
|
440
|
-
draggable: true,
|
|
441
|
-
onDragStart: () => { dragKey.current = model; },
|
|
442
|
-
onDragOver: (event) => event.preventDefault(),
|
|
443
|
-
onDrop: () => onRowDrop(model),
|
|
444
|
-
},
|
|
445
|
-
h("td", { className: c("drag"), title: "拖动排序" }, "⠿"),
|
|
446
|
-
h("td", null, h("input", { type: "checkbox", checked, onChange: () => togglePicked(model) })),
|
|
447
|
-
h("td", null, model),
|
|
448
|
-
matrix.tasks.map((taskKeyCol) => {
|
|
449
|
-
const row = matrix.cells.get(`${model}__@__${taskKeyCol}`);
|
|
450
|
-
let cellText = "×";
|
|
451
|
-
let title = "该模型未执行此任务";
|
|
452
|
-
if (row) {
|
|
453
|
-
if (row.answered > 0 && row.errors >= row.answered) {
|
|
454
|
-
cellText = `E (${row.errors})`;
|
|
455
|
-
title = "该任务全部回答均 API 失败($ERROR$),计 0 分";
|
|
456
|
-
} else if (row.judged === 0) {
|
|
457
|
-
cellText = "未判分";
|
|
458
|
-
title = "有答案但尚未判分";
|
|
459
|
-
} else {
|
|
460
|
-
cellText = `${row.score.toFixed(1)} (${row.judged}/${row.total})`;
|
|
461
|
-
if (row.errors > 0) title += ` · 其中 ${row.errors} 题 API 失败计 0 分`;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
return h("td", { key: taskKeyCol, className: c("score"), title }, cellText);
|
|
465
|
-
}),
|
|
466
|
-
h("td", null, h("button", {
|
|
467
|
-
className: c("btnGhost") + " " + c("btn"), title: "删除该模型的所有评测成绩",
|
|
468
|
-
onClick: () => deleteModels([model]),
|
|
469
|
-
}, "删除")),
|
|
470
|
-
h("td", null, fmtTime(startTime)),
|
|
471
|
-
);
|
|
472
|
-
})),
|
|
473
|
-
),
|
|
474
|
-
),
|
|
475
|
-
config !== null && config.available === false && h("div", { className: c("card") + " " + c("notice") },
|
|
476
|
-
h("p", { className: c("noticeTitle") }, "⚠ 本面板需要本地安装 LiveBench(当前未检测到)"),
|
|
477
|
-
h("p", { className: c("hint") },
|
|
478
|
-
"dsh-livebench-panel 只是控制台,真正的评测由 LiveBench 项目完成。面板会依次查找:本页「LiveBench 路径」输入框保存的目录、环境变量 DSH_LIVEBENCH_HOME、用户目录 ~/LiveBench(需含 .venv 与 livebench\\run_livebench.py)。按下面步骤安装:"),
|
|
479
|
-
h("ol", { className: c("steps") },
|
|
480
|
-
h("li", null, "克隆 LiveBench 仓库:",
|
|
481
|
-
h("code", { className: c("code") }, "git clone https://github.com/LiveBench/LiveBench V:\\PythonProject\\C_UtilizeSpace\\LiveBench")),
|
|
482
|
-
h("li", null, "用 Python 3.11 建虚拟环境并安装(3.10 会因 litellm 报 NotRequired 错误):",
|
|
483
|
-
h("code", { className: c("code") }, "cd /d V:\\PythonProject\\C_UtilizeSpace\\LiveBench && py -3.11 -m venv .venv"),
|
|
484
|
-
h("code", { className: c("code") }, ".venv\\Scripts\\python -m pip install -e .")),
|
|
485
|
-
h("li", null, "(评测 coding 类才需要)安装评分依赖:",
|
|
486
|
-
h("code", { className: c("code") }, ".venv\\Scripts\\python -m pip install -r livebench\\code_runner\\requirements_eval.txt")),
|
|
487
|
-
h("li", null, "下载题目数据:",
|
|
488
|
-
h("code", { className: c("code") }, "cd livebench && ..\\.venv\\Scripts\\python download_questions.py")),
|
|
489
|
-
h("li", null, "重启 dsh web,回到本页点「刷新」。",
|
|
490
|
-
h("br", null),
|
|
491
|
-
"若装在其它目录:在本页顶部「LiveBench 路径」输入框填入目录并点「设置路径」即可,也可用环境变量 DSH_LIVEBENCH_HOME。"),
|
|
492
|
-
),
|
|
493
|
-
h("p", { className: c("hint") },
|
|
494
|
-
"除 LiveBench 外无需其它配置:评测所需的 API Key 会自动从 harness 凭据库读取并注入;npm 安装本插件时可一并执行 dsh plugin --profile web add dsh-livebench-panel。完整说明见插件目录内 README:~\\.dsh\\plugins\\dsh-livebench-panel\\README.md"),
|
|
495
|
-
),
|
|
496
|
-
h("details", { className: c("details") },
|
|
497
|
-
h("summary", null, "❓ 使用说明 · 选项含义与选择建议(点击展开)"),
|
|
498
|
-
h("p", { className: c("helpP") },
|
|
499
|
-
"操作流程:选择参数 → 点「开始评测」→ 日志区实时显示运行进度 → 结束后成绩表自动刷新(运行中可「停止」)。全部判分均为客观比对(文本/符号执行/测试用例),不使用 AI 评分。"),
|
|
500
|
-
h("table", { className: c("helpTable") },
|
|
501
|
-
h("thead", null, h("tr", null, h("th", null, "选项"), h("th", null, "含义与选择建议"))),
|
|
502
|
-
h("tbody", null,
|
|
503
|
-
h("tr", null, h("th", null, "模型"), h("td", null,
|
|
504
|
-
"harness 全部 provider 的全部模型(含内置 DeepSeek 官方)。API Key 自动从 harness 凭据库读取并注入,无需手工配置;名字带「·未接LiveBench」的 provider 无法自动路由,评测会按模型名原生尝试。")),
|
|
505
|
-
h("tr", null, h("th", null, "推理强度"), h("td", null,
|
|
506
|
-
"在模型下拉中勾选模型后,于该行内的次级下拉单独设定;强度会编码进条目名(如 glm-5.3@max),不同强度在成绩表中是独立条目,方便对比。")),
|
|
507
|
-
h("tr", null, h("th", null, "题集 release"), h("td", null,
|
|
508
|
-
"题目发布批次。推荐 2024-11-25(公开题目最全)。LiveBench 每月换题:新批次下老任务会陆续退役,任务下拉括号内就是该批次下的有效题数。")),
|
|
509
|
-
h("tr", null, h("th", null, "分类 / 任务"), h("td", null,
|
|
510
|
-
"六大类共 18 个任务:coding(代码生成/补全)、math(竞赛数学等)、reasoning(空间推理/逻辑谜题)、language(拼写/连线/语义)、data_analysis(表格操作)、instruction_following(指令遵循)。首次验证推荐 language → typos。")),
|
|
511
|
-
h("tr", null, h("th", null, "题目序号范围"), h("td", null,
|
|
512
|
-
"从 0 起。冒烟测试填 0 到 2(只跑 3 题);2 题得分噪声很大(对一题就是 0↔100 的波动),想看真实水平建议 20 题以上。")),
|
|
513
|
-
h("tr", null, h("th", null, "max-tokens"), h("td", null,
|
|
514
|
-
"单题回答的 token 上限,默认 32000。推理模型思考也占 token,不要低于 8192,否则思考被截断、答案为空会记 0 分。")),
|
|
515
|
-
),
|
|
516
|
-
),
|
|
517
|
-
h("p", { className: c("helpP"), style: { marginTop: "10px" } },
|
|
518
|
-
"提示:回答为 $ERROR$ 表示该题 API 调用失败(网络/鉴权/参数问题)计 0 分,可在上方日志区查看具体错误;zebra_puzzle(逻辑谜题)是公认最难的任务,低分属正常现象。"),
|
|
519
|
-
),
|
|
520
|
-
h("p", { className: c("hint") },
|
|
521
|
-
"评分读取 LiveBench 的 ground_truth_judgment.jsonl;正式榜单可用 release 2024-11-25。多选题集请在「分类/任务」中缩小范围,避免长时间运行。"),
|
|
522
|
-
);
|