sparkle/mlx_streaming/server/admin.py
fiser_jun 4745f264b2
2026-08-04 14:34:00 +08:00

117 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""极简 admin 页:单文件 HTML + 内联 JS,不引框架/CDN。中文文案,2.5s 轮询 stats。"""
ADMIN_HTML = """<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sparkle 引擎控制台</title>
<style>
body { font-family: -apple-system, "PingFang SC", sans-serif; max-width: 640px;
margin: 32px auto; padding: 0 16px; color: #222; }
h1 { font-size: 20px; }
section { border: 1px solid #ddd; border-radius: 8px; padding: 16px;
margin-bottom: 16px; }
.row { margin: 8px 0; }
.label { display: inline-block; width: 180px; color: #555; }
input[type=number] { width: 90px; }
input[type=range] { width: 240px; vertical-align: middle; }
button { padding: 6px 20px; }
#msg { margin-left: 12px; color: #060; }
.hint { color: #888; font-size: 13px; }
</style>
</head>
<body>
<h1>Sparkle 引擎控制台</h1>
<section>
<div class="row"><span class="label">引擎状态</span><span id="loaded">-</span></div>
<div class="row"><span class="label">模型</span><span id="model">-</span></div>
<div class="row"><span class="label">最近一轮吞吐</span><span id="tps">-</span> tok/s</div>
<div class="row"><span class="label">进程内存 RSS</span><span id="rss">-</span></div>
</section>
<section>
<div class="row">
<span class="label">常驻专家池 expert_slots</span>
<input type="range" id="slots" min="32" max="160" step="1" value="32">
<b id="slotsVal">32</b>
</div>
<div class="row hint">预估池内存:<span id="poolMem"></span>(slots × ~160MB(g64));修改后引擎后台重建,期间对话不可用</div>
<div class="row">
<span class="label">投机宽度 k</span>
<input type="number" id="k" min="1" max="8" step="1" value="3">
</div>
<div class="row">
<span class="label">最大生成 max_tokens</span>
<input type="number" id="maxTokens" min="1" max="32768" step="1" value="4096">
</div>
<div class="row">
<button id="apply">应用</button><span id="msg"></span>
</div>
</section>
<script>
const $ = id => document.getElementById(id);
function fmtBytes(b) {
if (b >= 1024 * 1024 * 1024) return (b / 1024 / 1024 / 1024).toFixed(2) + " GB";
return (b / 1024 / 1024).toFixed(0) + " MB";
}
function updateSlots() {
const v = +$("slots").value;
$("slotsVal").textContent = v;
$("poolMem").textContent = " " + fmtBytes(v * 160 * 1024 * 1024) + " ";
}
$("slots").addEventListener("input", updateSlots);
async function refresh() {
try {
const s = await (await fetch("/api/stats")).json();
$("loaded").textContent = s.loaded ? "已加载" : "未加载 / 重建中";
$("model").textContent = s.model || "-";
$("tps").textContent = (s.tok_per_s || 0).toFixed(1);
$("rss").textContent = fmtBytes(s.rss_bytes || 0);
if (document.activeElement !== $("slots")) {
$("slots").value = s.expert_slots;
updateSlots();
}
} catch (e) { /* 服务暂不可达时下轮再试 */ }
}
$("apply").addEventListener("click", async () => {
const body = {
expert_slots: +$("slots").value,
k: +$("k").value,
max_tokens: +$("maxTokens").value
};
try {
const r = await (await fetch("/api/engine/config", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify(body)
})).json();
$("msg").textContent = r.reloading ? "已应用,引擎正在后台重建…" : "已应用";
} catch (e) {
$("msg").textContent = "应用失败";
}
setTimeout(() => { $("msg").textContent = ""; }, 4000);
});
(async function init() {
try {
const c = await (await fetch("/api/engine/config")).json();
$("slots").value = c.expert_slots;
$("k").value = c.k;
$("maxTokens").value = c.max_tokens;
} catch (e) { /* 用默认值 */ }
updateSlots();
refresh();
setInterval(refresh, 2500);
})();
</script>
</body>
</html>
"""