diff --git a/static/index.html b/static/index.html
index b9b9eec..158e229 100644
--- a/static/index.html
+++ b/static/index.html
@@ -116,6 +116,20 @@
+
+ 📤 评测站同步
+
+
+
+
+ 测试完成后,在历史列表或详情里点「📤 发送到评测站」,一键把结果发布到模型评测网站(端口 16066)对应账号下。
+
@@ -170,6 +184,7 @@
测试详情 #
+
diff --git a/static/js/app.js b/static/js/app.js
index a5a8d15..6a589d7 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -383,6 +383,7 @@ async function loadHistory() {
${STATUS_LABEL[t.status] || t.status} |
+
| `;
@@ -824,6 +825,92 @@ function exportCurrentLog() {
});
}
+/* ───────────────────────── 评测站同步(一键发送) ───────────────────────── */
+
+const EVAL_TOKEN = "meval16066"; // 与评测站 config.SUBMIT_TOKEN 一致
+
+function evalSettings() {
+ return {
+ url: (localStorage.getItem("evalUrl") || "http://127.0.0.1:16066").replace(/\/+$/, ""),
+ account: localStorage.getItem("evalAccount") || "默认账号",
+ };
+}
+
+function loadEvalSettings() {
+ const s = evalSettings();
+ const u = $("#eval-url"), a = $("#eval-account");
+ if (u) u.value = s.url;
+ if (a) a.value = s.account;
+}
+
+function saveEvalSettings() {
+ localStorage.setItem("evalUrl", $("#eval-url").value.trim() || "http://127.0.0.1:16066");
+ localStorage.setItem("evalAccount", $("#eval-account").value.trim() || "默认账号");
+}
+
+async function testEvalConn() {
+ saveEvalSettings();
+ const s = evalSettings();
+ const box = $("#eval-result");
+ box.hidden = false; box.className = "conn-result";
+ box.textContent = "⏳ 正在连接评测站...";
+ try {
+ const resp = await fetch(s.url + "/api/health");
+ if (!resp.ok) throw new Error("HTTP " + resp.status);
+ const j = await resp.json();
+ const st = j.stats || {};
+ box.className = "conn-result ok";
+ box.textContent = `✅ 连接成功(端口 ${j.port})| 模型 ${st.models} · 提交 ${st.submissions} · 账号 ${st.accounts}`;
+ } catch (e) {
+ box.className = "conn-result fail";
+ box.textContent = "❌ 连接失败:" + e.message;
+ }
+}
+
+async function sendToEval(id) {
+ saveEvalSettings();
+ const s = evalSettings();
+ if (!confirm(`将测试 #${id} 发送到评测站(${s.url})的账号「${s.account}」下?`)) return;
+ let t;
+ try {
+ const resp = await fetch(`/api/tests/${id}/export.json`);
+ if (!resp.ok) throw new Error("读取测试数据失败");
+ t = await resp.json();
+ } catch (e) {
+ toast("读取失败:" + e.message); return;
+ }
+ if (!(t.summary && t.summary.samples_ok > 0)) {
+ toast("该测试没有成功采样数据,无法发送"); return;
+ }
+ const payload = {
+ account: s.account,
+ source_test_id: id,
+ source_site: "llm-speed-tester",
+ provider: t.provider || "",
+ model: t.model || "",
+ test_name: t.name || "",
+ summary: t.summary || {},
+ gen: t.gen || {},
+ config: t.config || {},
+ runs: t.runs || [],
+ };
+ try {
+ const resp = await fetch(s.url + "/api/submit", {
+ method: "POST",
+ headers: { "Content-Type": "application/json", "X-Token": EVAL_TOKEN },
+ body: JSON.stringify(payload),
+ });
+ const j = await resp.json().catch(() => ({}));
+ if (!j.ok) throw new Error(j.error || "发送失败(HTTP " + resp.status + ")");
+ toast(`✅ 已发送!评测站提交 #${j.id}(账号 ${j.account} / ${j.model})`);
+ setTimeout(() => {
+ toast(`📤 查看详情:${s.url}${j.url}`);
+ }, 3600);
+ } catch (e) {
+ toast("❌ 发送失败:" + e.message);
+ }
+}
+
/* ───────────────────────── 提示 ───────────────────────── */
function toast(msg) {
@@ -863,6 +950,7 @@ function bind() {
});
$("#btn-test-conn").addEventListener("click", testConnection);
+ $("#btn-eval-test").addEventListener("click", testEvalConn);
$("#btn-start").addEventListener("click", startTest);
$("#btn-cancel").addEventListener("click", stopTest);
$("#btn-context-add").addEventListener("click", addContextLength);
@@ -877,9 +965,11 @@ function bind() {
$("#history tbody").addEventListener("click", (e) => {
const v = e.target.closest("[data-view]");
+ const s = e.target.closest("[data-send]");
const x = e.target.closest("[data-xlsx]");
const d = e.target.closest("[data-del]");
if (v) viewDetail(Number(v.dataset.view));
+ if (s) sendToEval(Number(s.dataset.send));
if (x) exportXlsx(Number(x.dataset.xlsx));
if (d) {
const id = Number(d.dataset.del);
@@ -911,6 +1001,7 @@ function bind() {
$("#cmp-mask").addEventListener("click", (e) => { if (e.target === $("#cmp-mask")) closeCompare(); });
$("#dt-export").addEventListener("click", exportDetail);
$("#dt-export-xlsx").addEventListener("click", () => { if (window.__detail) exportXlsx(window.__detail.id); });
+ $("#dt-send-eval").addEventListener("click", () => { if (window.__detail) sendToEval(window.__detail.id); });
document.addEventListener("keydown", (e) => { if (e.key === "Escape") { closeDetail(); closeCompare(); } });
}
@@ -924,5 +1015,6 @@ function bind() {
updateDefaultUrlHint();
loadConfigs();
loadHistory();
+ loadEvalSettings();
setInterval(() => { if (!currentTestId) loadHistory(); }, 30000); // 空闲时定期刷新历史
})();