stats: per-agent /stats page with chart.js trends + breakdowns
new hive-ag3nt::stats module reads turn_stats.sqlite read-only and aggregates over 24h/7d/30d windows (hourly/daily buckets) — turn rate, p50/p95/avg duration, ctx tokens (avg/max), cost token components, top tools, wake mix, result mix. served by the agent itself so per-MCP extensions can register more providers without the host knowing their schemas. /stats route + /api/stats?window=... on the per-agent web ui. chart.js v4.4.4 pulled from jsdelivr (SRI hash deferred). nav links: 📊 chip on the dashboard container row + 📊 stats → on the per-agent header. todo housekeeping: softened damocles-area note at the top, new reverse-proxy + deferred reminder-rollup items, removed the two telemetry-ui items absorbed by this page.
This commit is contained in:
parent
f9f1346eae
commit
d3f90f4cc0
8 changed files with 930 additions and 3 deletions
|
|
@ -107,6 +107,9 @@ pub async fn serve(
|
|||
.route("/api/model", post(post_set_model))
|
||||
.route("/api/new-session", post(post_new_session))
|
||||
.route("/api/loose-ends", get(api_loose_ends))
|
||||
.route("/stats", get(serve_stats))
|
||||
.route("/static/stats.js", get(serve_stats_js))
|
||||
.route("/api/stats", get(api_stats))
|
||||
.with_state(state);
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], port));
|
||||
let listener = bind_with_retry(addr, "web UI").await?;
|
||||
|
|
@ -198,6 +201,33 @@ async fn serve_marked_js() -> impl IntoResponse {
|
|||
)
|
||||
}
|
||||
|
||||
async fn serve_stats() -> impl IntoResponse {
|
||||
(
|
||||
[("content-type", "text/html; charset=utf-8")],
|
||||
include_str!("../assets/stats.html"),
|
||||
)
|
||||
}
|
||||
|
||||
async fn serve_stats_js() -> impl IntoResponse {
|
||||
(
|
||||
[("content-type", "application/javascript")],
|
||||
include_str!("../assets/stats.js"),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct StatsQuery {
|
||||
window: Option<String>,
|
||||
}
|
||||
|
||||
async fn api_stats(
|
||||
State(_state): State<AppState>,
|
||||
axum::extract::Query(q): axum::extract::Query<StatsQuery>,
|
||||
) -> axum::Json<crate::stats::Snapshot> {
|
||||
let window = crate::stats::Window::parse(q.window.as_deref().unwrap_or("24h"));
|
||||
axum::Json(crate::stats::snapshot_default(window))
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct StateSnapshot {
|
||||
/// Bus seq at the moment this snapshot was assembled. Clients dedupe
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue