feat: add per-source TTFB and tokens/s metrics to status page

- Req: add FirstByteMs field (ms to first byte, tracked for streaming)
- Stat: add FirstByteSum for aggregation
- SourceAverages(): new method computing per-source avg TTFB and tokens/s
  from the in-memory ring (300s window)
- SourceStatus: add AvgFirstByteMs and AvgTokPerS fields
- pumpStream: record FirstByteMs after first SSE chunk sent to client
- singleChat/singleChatAuto: set FirstByteMs = LatMs (non-streaming)
- handleStatusAPI: populate the new SourceStatus fields from SourceAverages()
- WebUI source table: two new columns showing TTFB (s) and Tokens/s
This commit is contained in:
dev
2026-08-25 08:24:08 +08:00
parent 2a5c7bbbc7
commit 18c2385c61
7 changed files with 118 additions and 20 deletions

View File

@ -774,6 +774,8 @@
tURL: "地址",
tConn: "连接",
tConc: "并发",
tAvgLat: "首字延迟",
tTokSpd: "Token/s",
online: "在线",
offline: "退避 / 不可用",
adTitle: "已加载适配器",
@ -971,6 +973,8 @@
tURL: "URL",
tConn: "Status",
tConc: "Concurrency",
tAvgLat: "TTFB",
tTokSpd: "Tokens/s",
online: "online",
offline: "backoff / down",
adTitle: "Loaded adapters",
@ -1374,7 +1378,9 @@
<td><b>${esc(x.name)}</b></td><td>${esc(x.adapter)}</td>
<td><div class="src-models">${(x.models || []).map((m) => `<span class="tag tag-blue modelchip" onclick="showModelConfig('${escAttr(x.name)}','${escAttr(m)}')">${esc(m)}</span>`).join("")}</div></td>
<td><span class="muted">${esc(x.base_url || "")}</span></td>
<td>${x.max_concurrent}</td></tr>`,
<td>${x.max_concurrent}</td>
<td>${x.avg_first_byte_ms ? (x.avg_first_byte_ms / 1000).toFixed(1) + "s" : "—"}</td>
<td>${x.avg_tok_per_s ? x.avg_tok_per_s + " tok/s" : "—"}</td></tr>`,
)
.join("");
} catch (e) {
@ -1422,7 +1428,7 @@
${
s.sources
? `<div class="card"><h2>${t("srcTitle")} (${s.sources.length})</h2>
<div class="tbl-wrap"><table><tr><th>${t("tConn")}</th><th>${t("tName")}</th><th>${t("tAdapter")}</th><th>${t("tModels")}</th><th>${t("tURL")}</th><th>${t("tConc")}</th></tr><tbody id="src-body"></tbody></table></div>
<div class="tbl-wrap"><table><tr><th>${t("tConn")}</th><th>${t("tName")}</th><th>${t("tAdapter")}</th><th>${t("tModels")}</th><th>${t("tURL")}</th><th>${t("tConc")}</th><th>${t("tAvgLat")}</th><th>${t("tTokSpd")}</th></tr><tbody id="src-body"></tbody></table></div>
<p class="muted" style="padding:6px 2px 0"><button class="ghost small" onclick="paintSourceStatus()">${t("refreshSrc") || "刷新源状态"}</button> 状态列每 5 秒自动刷新</p>
</div>`
: ""