v1.3.0 热度排行+搜索+详情+水印下载:速度项按钮改+对比(去购物车图标);排行默认按热度显示前10(后台可配)+顶部模型搜索框(点击显示热门5个:配置简介+模型,支持手动搜索,速度项热度/个数/简介后台可改);每行详情链接到具体详情页;图表带水印(默认首页网址,后台可配)+生成图片下载按钮(首页/对比页)

This commit is contained in:
2026-09-02 17:51:56 +08:00
parent 79f59be6d6
commit b0dd109a13
8 changed files with 615 additions and 39 deletions
+174 -4
View File
@@ -61,6 +61,23 @@ CREATE TABLE IF NOT EXISTS capabilities(
updated_at TEXT DEFAULT (datetime('now','localtime'))
);
CREATE TABLE IF NOT EXISTS speed_items(
id INTEGER PRIMARY KEY AUTOINCREMENT,
provider TEXT DEFAULT '',
model TEXT DEFAULT '',
heat INTEGER DEFAULT 0,
intro TEXT DEFAULT '',
count INTEGER DEFAULT 0,
remark TEXT DEFAULT '',
updated_at TEXT DEFAULT (datetime('now','localtime'))
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_si_model ON speed_items(provider, model);
CREATE TABLE IF NOT EXISTS settings(
key TEXT PRIMARY KEY,
value TEXT DEFAULT ''
);
CREATE INDEX IF NOT EXISTS idx_sub_model ON submissions(model, provider);
CREATE INDEX IF NOT EXISTS idx_sub_account ON submissions(account_id);
CREATE INDEX IF NOT EXISTS idx_sub_created ON submissions(created_at);
@@ -313,14 +330,16 @@ def get_stats():
conn.close()
def leaderboard(sort="avg_decode_speed", order="desc", limit=200):
"""按 (provider, model) 聚合所有提交 → 模型速度排行(含硬件)"""
def leaderboard(sort="heat", order="desc", limit=500):
"""按 (provider, model) 聚合所有提交 → 模型速度排行(含热度/配置简介/硬件)
默认按热度(heat)倒序;sort 支持 heat/avg_decode_speed 等"""
sort_whitelist = {
"heat": "heat",
"avg_decode_speed": "avg_decode_speed", "avg_prefill_speed": "avg_prefill_speed",
"avg_ttft_ms": "avg_ttft_ms", "best_decode": "best_decode",
"cnt": "cnt", "last_tested": "last_tested", "avg_stream_decode": "avg_stream_decode",
}
srt = sort_whitelist.get(sort, "avg_decode_speed")
srt = sort_whitelist.get(sort, "heat")
od = "DESC" if order == "asc" and srt not in ("avg_ttft_ms", "avg_total_ms") else (
"ASC" if order == "asc" else "DESC")
with _lock:
@@ -338,10 +357,15 @@ def leaderboard(sort="avg_decode_speed", order="desc", limit=200):
"AVG(s.avg_total_ms) AS avg_total_ms, "
"MAX(s.avg_decode_speed) AS best_decode, "
"MAX(s.created_at) AS last_tested, "
"COALESCE(MAX(si.heat), 0) AS heat, "
"MAX(si.intro) AS intro, "
"COALESCE(MAX(si.count), 0) AS count_override, "
"(SELECT s2.hardware FROM submissions s2 "
" WHERE s2.provider=s.provider AND s2.model=s.model AND s2.hardware<>'' "
" ORDER BY s2.id DESC LIMIT 1) AS hardware "
"FROM submissions s WHERE s.model<>'' "
"FROM submissions s "
"LEFT JOIN speed_items si ON si.provider=s.provider AND si.model=s.model "
"WHERE s.model<>'' "
"GROUP BY s.provider, s.model ORDER BY %s %s LIMIT ?" % (srt, od),
(limit,)).fetchall()
return [dict(r) for r in rows]
@@ -502,3 +526,149 @@ def delete_capability(cid: int):
conn.commit()
finally:
conn.close()
# ───────────────────────── 速度项(热度/配置简介/个数,后台可改) ─────────────────────────
def touch_speed_item(provider, model):
"""提交到达时确保速度项存在;不存在则以当前提交数为默认热度"""
provider = provider or ""
model = (model or "").strip()
if not model:
return None
with _lock:
conn = _connect()
try:
r = conn.execute("SELECT id FROM speed_items WHERE provider=? AND model=?",
(provider, model)).fetchone()
if r:
return r["id"]
cnt = conn.execute("SELECT COUNT(*) c FROM submissions WHERE provider=? AND model=?",
(provider, model)).fetchone()["c"]
cur = conn.execute(
"INSERT INTO speed_items(provider,model,heat,intro,count) VALUES(?,?,?,?,?)",
(provider, model, cnt, "", cnt))
conn.commit()
return cur.lastrowid
finally:
conn.close()
def list_speed_items():
with _lock:
conn = _connect()
try:
rows = conn.execute(
"SELECT si.*, "
"(SELECT COUNT(*) FROM submissions s WHERE s.provider=si.provider AND s.model=si.model) AS real_cnt "
"FROM speed_items si ORDER BY si.heat DESC, si.model").fetchall()
return [dict(r) for r in rows]
finally:
conn.close()
def add_speed_item(provider, model, heat=0, intro="", count=0, remark=""):
provider = provider or ""
model = (model or "").strip()
if not model:
raise ValueError("模型名称不能为空")
with _lock:
conn = _connect()
try:
r = conn.execute("SELECT id FROM speed_items WHERE provider=? AND model=?",
(provider, model)).fetchone()
if r:
raise ValueError("该速度项已存在")
cur = conn.execute(
"INSERT INTO speed_items(provider,model,heat,intro,count,remark) VALUES(?,?,?,?,?,?)",
(provider, model, int(heat or 0), intro or "", int(count or 0), remark or ""))
conn.commit()
return cur.lastrowid
finally:
conn.close()
def update_speed_item(sid: int, data: dict):
with _lock:
conn = _connect()
try:
conn.execute(
"UPDATE speed_items SET provider=?,model=?,heat=?,intro=?,count=?,remark=?,"
"updated_at=datetime('now','localtime') WHERE id=?",
((data.get("provider") or "").strip(),
(data.get("model") or "").strip(),
int(data.get("heat") or 0),
(data.get("intro") or "").strip(),
int(data.get("count") or 0),
(data.get("remark") or "").strip(),
sid))
conn.commit()
finally:
conn.close()
def delete_speed_item(sid: int):
with _lock:
conn = _connect()
try:
conn.execute("DELETE FROM speed_items WHERE id=?", (sid,))
conn.commit()
finally:
conn.close()
def sync_speed_items():
"""从提交自动同步速度项:为所有有提交的模型建速度项(默认热度=提交数)"""
added = 0
with _lock:
conn = _connect()
try:
rows = conn.execute(
"SELECT provider, model, COUNT(*) c FROM submissions WHERE model<>'' "
"GROUP BY provider, model").fetchall()
for r in rows:
r0 = conn.execute("SELECT id FROM speed_items WHERE provider=? AND model=?",
(r["provider"], r["model"])).fetchone()
if r0:
continue
conn.execute(
"INSERT INTO speed_items(provider,model,heat,intro,count) VALUES(?,?,?,?,?)",
(r["provider"], r["model"], r["c"], "", r["c"]))
added += 1
conn.commit()
return added
finally:
conn.close()
# ───────────────────────── 设置(水印/默认条数等) ─────────────────────────
def get_setting(key, default=""):
with _lock:
conn = _connect()
try:
r = conn.execute("SELECT value FROM settings WHERE key=?", (key,)).fetchone()
return r["value"] if r else default
finally:
conn.close()
def set_setting(key, value):
with _lock:
conn = _connect()
try:
conn.execute("INSERT OR REPLACE INTO settings(key,value) VALUES(?,?)",
(key, str(value or "")))
conn.commit()
finally:
conn.close()
def get_all_settings():
with _lock:
conn = _connect()
try:
rows = conn.execute("SELECT key,value FROM settings").fetchall()
return {r["key"]: r["value"] for r in rows}
finally:
conn.close()