feat: token count implement

This commit is contained in:
2026-03-17 12:07:56 -03:00
parent 41279585f6
commit be699ad6ab
44 changed files with 9032 additions and 167 deletions
+136
View File
@@ -0,0 +1,136 @@
use axum::{
Json,
extract::{Query, State},
http::StatusCode,
};
use common::{auth::Claims, middleware::Org};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
// ==================== Token Usage Tracking ====================
/// GET /api/admin/token-usage - Get token usage statistics for all users
pub async fn get_token_usage(
_org_ctx: Org,
_claims: Claims,
State(pool): State<PgPool>,
) -> Result<Json<TokenUsageResponse>, (StatusCode, String)> {
// Get user token usage from database
let usage: Vec<TokenUsageRecord> = sqlx::query_as(
r#"
SELECT
u.id as user_id,
u.email,
u.full_name,
u.role,
COALESCE(SUM(au.tokens_used), 0) as total_tokens,
COALESCE(SUM(au.input_tokens), 0) as input_tokens,
COALESCE(SUM(au.output_tokens), 0) as output_tokens,
COUNT(au.id) as ai_requests,
MAX(au.created_at) as last_used
FROM users u
LEFT JOIN ai_usage_logs au ON u.id = au.user_id
GROUP BY u.id, u.email, u.full_name, u.role
ORDER BY total_tokens DESC
"#
)
.fetch_all(&pool)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to fetch usage: {}", e)))?;
// Calculate stats
let total_tokens: i64 = usage.iter().map(|u| u.total_tokens).sum();
let total_input: i64 = usage.iter().map(|u| u.input_tokens).sum();
let total_output: i64 = usage.iter().map(|u| u.output_tokens).sum();
let total_requests: i64 = usage.iter().map(|u| u.ai_requests).sum();
let top_user_tokens = usage.first().map(|u| u.total_tokens).unwrap_or(0);
let avg_tokens = if !usage.is_empty() { total_tokens / usage.len() as i64 } else { 0 };
// Estimate cost (using approximate OpenAI pricing: $0.001/1K input, $0.003/1K output)
let estimated_cost = (total_input as f64 * 0.000001) + (total_output as f64 * 0.000003);
let stats = TokenUsageStats {
total_tokens,
total_input,
total_output,
total_requests,
total_cost_usd: estimated_cost,
top_user_tokens,
avg_tokens_per_user: avg_tokens,
};
// Convert to response format with USD estimation per user
let usage_with_cost: Vec<TokenUsage> = usage
.into_iter()
.map(|u| {
let user_cost = (u.input_tokens as f64 * 0.000001) + (u.output_tokens as f64 * 0.000003);
TokenUsage {
user_id: u.user_id.to_string(),
email: u.email,
full_name: u.full_name,
role: u.role,
total_tokens: u.total_tokens,
input_tokens: u.input_tokens,
output_tokens: u.output_tokens,
ai_requests: u.ai_requests,
last_used: u.last_used.to_rfc3339(),
estimated_cost_usd: user_cost,
}
})
.collect();
Ok(Json(TokenUsageResponse {
usage: usage_with_cost,
stats,
}))
}
#[derive(Debug, Deserialize)]
pub struct TokenUsageFilters {
pub role: Option<String>,
pub min_tokens: Option<i64>,
}
#[derive(Debug, Serialize)]
pub struct TokenUsage {
pub user_id: String,
pub email: String,
pub full_name: String,
pub role: String,
pub total_tokens: i64,
pub input_tokens: i64,
pub output_tokens: i64,
pub ai_requests: i64,
pub last_used: String,
pub estimated_cost_usd: f64,
}
#[derive(Debug, sqlx::FromRow)]
struct TokenUsageRecord {
user_id: uuid::Uuid,
email: String,
full_name: String,
role: String,
total_tokens: i64,
input_tokens: i64,
output_tokens: i64,
ai_requests: i64,
last_used: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Serialize)]
pub struct TokenUsageStats {
pub total_tokens: i64,
pub total_input: i64,
pub total_output: i64,
pub total_requests: i64,
pub total_cost_usd: f64,
pub top_user_tokens: i64,
pub avg_tokens_per_user: i64,
}
#[derive(Debug, Serialize)]
pub struct TokenUsageResponse {
pub usage: Vec<TokenUsage>,
pub stats: TokenUsageStats,
}