v1.4.0 全局搜索+水印5风格+对比列表管理+排行列可配:搜索框拆分(网页顶部居中=选中跳详情; 排行内搜索=添加速度项到本区域查看,可✕移除); 水印预置5种风格(右下角/全图斜纹/底部横条/四角/对角大字)后台可选; 运行速度对比图要对比模型列表可叉掉/清空一键去除/+对比添加; 图表改深色主题适配深色背景; 排行榜新增推理框架列,首字ms默认隐藏,平均解码+最佳解码合并,硬件+推理框架合并,列显示/合并后台⚙️设置可配

This commit is contained in:
2026-09-03 00:50:05 +08:00
parent ad863077c6
commit 3a6a1e02a2
7 changed files with 407 additions and 184 deletions
+71 -24
View File
@@ -31,6 +31,11 @@ def _watermark_text():
return db.get_setting("watermark_text", config.DEFAULT_WATERMARK)
def _watermark_style():
st = db.get_setting("watermark_style", config.DEFAULT_WATERMARK_STYLE)
return st if st in config.WATERMARK_STYLES else config.DEFAULT_WATERMARK_STYLE
def _top_n():
try:
return max(1, int(db.get_setting("top_n", str(config.DEFAULT_TOP_N))))
@@ -38,10 +43,22 @@ def _top_n():
return config.DEFAULT_TOP_N
def apply_watermark(img_bytes: bytes, text: str) -> bytes:
"""给图片叠加简单水印:全图浅淡斜纹 + 右下角标注。颜色自适应当前背景(浅底深字/深底浅字)"""
def _column_config():
out = {}
for k, default in config.COLUMN_DEFAULTS.items():
v = db.get_setting(k, default)
out[k] = 1 if str(v).strip() in ("1", "true", "on") else 0
return out
def apply_watermark(img_bytes: bytes, text: str, style: str = "") -> bytes:
"""给图片叠加水印,5 种风格可选:corner右下角标注 / diagonal全图斜纹 / bottom底部横条 / corners四角标注 / diag-big对角大字
颜色自适应:浅底深字 / 深底浅字"""
if not text:
return img_bytes
style = style or config.DEFAULT_WATERMARK_STYLE
if style not in config.WATERMARK_STYLES:
style = config.DEFAULT_WATERMARK_STYLE
try:
from PIL import Image, ImageDraw, ImageFont
except Exception:
@@ -51,34 +68,59 @@ def apply_watermark(img_bytes: bytes, text: str) -> bytes:
except Exception:
return img_bytes
w, h = img.size
# 自适应水印颜色:取样左上角判断背景深浅
bg = img.getpixel((4, 4))[:3]
bg_luma = 0.299 * bg[0] + 0.587 * bg[1] + 0.114 * bg[2]
dark_bg = bg_luma < 140
fill = (50, 50, 60) if not dark_bg else (255, 255, 255)
diag_alpha = 30 if not dark_bg else 26
corner_alpha = 120 if not dark_bg else 128
a_diag = 30 if not dark_bg else 26
a_main = 130 if not dark_bg else 135
overlay = Image.new("RGBA", (w, h), (0, 0, 0, 0))
d = ImageDraw.Draw(overlay)
try:
font = ImageFont.truetype(_WATERMARK_FONT, 16)
font_big = ImageFont.truetype(_WATERMARK_FONT, 22)
font_big = ImageFont.truetype(_WATERMARK_FONT, 24)
font_huge = ImageFont.truetype(_WATERMARK_FONT, 60)
except Exception:
font = ImageFont.load_default()
font_big = ImageFont.load_default()
font_huge = ImageFont.load_default()
# 全图浅淡斜纹水印(45° 重复)
step = 170
for y in range(-h, h, step):
for x in range(-w, w, step):
d.text((x + y * 0.4, y), text, font=font, fill=fill + (diag_alpha,))
def _corner_label():
box = d.textbbox((0, 0), text, font=font_big)
tw = box[2] - box[0]; th = box[3] - box[1]
d.text((w - tw - 16, h - th - 12), text, font=font_big, fill=fill + (a_main,))
# 右下角较清晰标注
box = d.textbbox((0, 0), text, font=font_big)
tw = box[2] - box[0]
th = box[3] - box[1]
d.text((w - tw - 16, h - th - 12), text, font=font_big, fill=fill + (corner_alpha,))
if style == "corner":
_corner_label()
elif style == "diagonal":
step = 170
for y in range(-h, h, step):
for x in range(-w, w, step):
d.text((x + y * 0.4, y), text, font=font, fill=fill + (a_diag,))
elif style == "bottom":
# 底部半透明横条
box = d.textbbox((0, 0), text, font=font_big)
tw = box[2] - box[0]; th = box[3] - box[1]
bh = th + 24
d.rectangle([0, h - bh, w, h], fill=(0, 0, 0, 90) if not dark_bg else (255, 255, 255, 70))
d.text((16, h - bh + 10), text, font=font_big, fill=fill + (a_main,))
# 底部中央小字
cx = (w - tw) // 2
d.text((cx, h - 20), text, font=font_big, fill=fill + (110,))
elif style == "corners":
box = d.textbbox((0, 0), text, font=font_big)
tw = box[2] - box[0]; th = box[3] - box[1]
for px, py in [(12, 12), (w - tw - 12, 12), (12, h - th - 12), (w - tw - 12, h - th - 12)]:
d.text((px, py), text, font=font_big, fill=fill + (a_main,))
elif style == "diag-big":
box = d.textbbox((0, 0), text, font=font_huge)
tw = box[2] - box[0]; th = box[3] - box[1]
for i in range(-1, 2):
d.text(((w - tw) // 2 + i * 14, (h - th) // 2 + i * 28), text, font=font_huge, fill=fill + (38,))
d.text(((w - tw) // 2, (h - th) // 2), text, font=font_huge, fill=fill + (80,))
else:
_corner_label()
out = Image.alpha_composite(img, overlay)
buf = io.BytesIO()
@@ -209,8 +251,10 @@ def leaderboard():
chart_rows = rows[: int(request.args.get("chart_top", 30) or 30)]
chart_csv = _speed_csv(chart_rows)
cols = _column_config()
return jsonify({"ok": True, "rows": rows, "chart_csv": chart_csv, "top_n": _top_n(),
"watermark": _watermark_text()})
"watermark": _watermark_text(), "watermark_style": _watermark_style(),
"columns": cols})
def _speed_csv(chart_rows):
@@ -271,7 +315,7 @@ def model_detail():
line_payload = {
"data": line_csv, "chartType": "line",
"title": "%s %s · 解码速度随上下文长度变化" % (provider or "", model),
"theme": "default", "showLegend": True, "showGrid": True, "showLabel": False,
"theme": "dark", "showLegend": True, "showGrid": True, "showLabel": False,
"smoothLine": True, "dualYAxis": True,
"leftAxisName": "预填充速度(tok/s)", "rightAxisName": "解码速度(tok/s)",
"seriesTypes": ["line", "line"], "seriesAxis": [0, 1],
@@ -341,7 +385,7 @@ def submission_chart(sid):
payload = {
"data": "\n".join(csv_lines), "chartType": "line",
"title": "%s %s · 速度随上下文长度(提交#%d" % (s["provider"], s["model"], sid),
"theme": "default", "showLegend": True, "showGrid": True, "showLabel": False,
"theme": "dark", "showLegend": True, "showGrid": True, "showLabel": False,
"smoothLine": True, "dualYAxis": True,
"leftAxisName": "预填充速度(tok/s)", "rightAxisName": "解码速度(tok/s)",
"seriesTypes": ["line", "line"], "seriesAxis": [0, 1],
@@ -389,7 +433,9 @@ def account_delete(aid):
@app.route("/api/settings")
def public_settings():
return jsonify({"ok": True, "watermark": _watermark_text(), "top_n": _top_n()})
return jsonify({"ok": True, "watermark": _watermark_text(),
"watermark_style": _watermark_style(),
"top_n": _top_n(), "columns": _column_config()})
@app.route("/api/admin/speed-items")
@@ -437,14 +483,15 @@ def admin_speed_item_sync():
@admin_required
def admin_settings_get():
return jsonify({"ok": True, "settings": db.get_all_settings(),
"watermark": _watermark_text(), "top_n": _top_n()})
"watermark": _watermark_text(), "watermark_style": _watermark_style(),
"top_n": _top_n(), "columns": _column_config()})
@app.route("/api/admin/settings", methods=["PUT"])
@admin_required
def admin_settings_put():
body = request.get_json(force=True) or {}
allow = {"watermark_text", "top_n"}
allow = {"watermark_text", "watermark_style", "top_n"} | set(config.COLUMN_DEFAULTS.keys())
for k, v in body.items():
if k in allow:
db.set_setting(k, v)
@@ -500,8 +547,8 @@ def _chart_proxy(payload):
return jsonify({"ok": False, "error": "图表服务不可用: %s" % e}), 502
if resp.status_code != 200:
return jsonify({"ok": False, "error": "图表生成失败(%d): %s" % (resp.status_code, resp.text[:300])}), 502
# 叠加水印(默认首页网址,后台可配置)
data = apply_watermark(resp.content, _watermark_text())
# 叠加水印(默认首页网址,后台可配置文字+5种风格
data = apply_watermark(resp.content, _watermark_text(), _watermark_style())
return send_file(io.BytesIO(data), mimetype="image/png")