feat: implement analytics and cost management dashboard pages
This commit is contained in:
@@ -67,6 +67,8 @@ pub fn router(state: AppState) -> Router {
|
||||
.route("/api/usage/time-series", get(handle_time_series))
|
||||
.route("/api/usage/clients", get(handle_clients_usage))
|
||||
.route("/api/usage/providers", get(handle_providers_usage))
|
||||
.route("/api/usage/detailed", get(handle_detailed_usage))
|
||||
.route("/api/analytics/breakdown", get(handle_analytics_breakdown))
|
||||
.route("/api/models", get(handle_get_models))
|
||||
.route("/api/models/{id}", put(handle_update_model))
|
||||
.route("/api/clients", get(handle_get_clients).post(handle_create_client))
|
||||
@@ -405,6 +407,83 @@ async fn handle_providers_usage(State(state): State<DashboardState>) -> Json<Api
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_detailed_usage(State(state): State<DashboardState>) -> Json<ApiResponse<serde_json::Value>> {
|
||||
let pool = &state.app_state.db_pool;
|
||||
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
SELECT
|
||||
strftime('%Y-%m-%d', timestamp) as date,
|
||||
client_id,
|
||||
provider,
|
||||
model,
|
||||
COUNT(*) as requests,
|
||||
SUM(total_tokens) as tokens,
|
||||
SUM(cost) as cost
|
||||
FROM llm_requests
|
||||
GROUP BY date, client_id, provider, model
|
||||
ORDER BY date DESC
|
||||
LIMIT 100
|
||||
"#
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(rows) => {
|
||||
let usage: Vec<serde_json::Value> = rows.into_iter().map(|row| {
|
||||
serde_json::json!({
|
||||
"date": row.get::<String, _>("date"),
|
||||
"client": row.get::<String, _>("client_id"),
|
||||
"provider": row.get::<String, _>("provider"),
|
||||
"model": row.get::<String, _>("model"),
|
||||
"requests": row.get::<i64, _>("requests"),
|
||||
"tokens": row.get::<i64, _>("tokens"),
|
||||
"cost": row.get::<f64, _>("cost"),
|
||||
})
|
||||
}).collect();
|
||||
|
||||
Json(ApiResponse::success(serde_json::json!(usage)))
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to fetch detailed usage: {}", e);
|
||||
Json(ApiResponse::error("Failed to fetch detailed usage".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_analytics_breakdown(State(state): State<DashboardState>) -> Json<ApiResponse<serde_json::Value>> {
|
||||
let pool = &state.app_state.db_pool;
|
||||
|
||||
// Model breakdown
|
||||
let models = sqlx::query(
|
||||
"SELECT model as label, COUNT(*) as value FROM llm_requests GROUP BY model ORDER BY value DESC"
|
||||
).fetch_all(pool);
|
||||
|
||||
// Client breakdown
|
||||
let clients = sqlx::query(
|
||||
"SELECT client_id as label, COUNT(*) as value FROM llm_requests GROUP BY client_id ORDER BY value DESC"
|
||||
).fetch_all(pool);
|
||||
|
||||
match tokio::join!(models, clients) {
|
||||
(Ok(m_rows), Ok(c_rows)) => {
|
||||
let model_breakdown: Vec<serde_json::Value> = m_rows.into_iter().map(|r| {
|
||||
serde_json::json!({ "label": r.get::<String, _>("label"), "value": r.get::<i64, _>("value") })
|
||||
}).collect();
|
||||
|
||||
let client_breakdown: Vec<serde_json::Value> = c_rows.into_iter().map(|r| {
|
||||
serde_json::json!({ "label": r.get::<String, _>("label"), "value": r.get::<i64, _>("value") })
|
||||
}).collect();
|
||||
|
||||
Json(ApiResponse::success(serde_json::json!({
|
||||
"models": model_breakdown,
|
||||
"clients": client_breakdown
|
||||
})))
|
||||
}
|
||||
_ => Json(ApiResponse::error("Failed to fetch analytics breakdown".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
// Client handlers
|
||||
async fn handle_get_clients(State(state): State<DashboardState>) -> Json<ApiResponse<serde_json::Value>> {
|
||||
let pool = &state.app_state.db_pool;
|
||||
|
||||
Reference in New Issue
Block a user