diff --git a/crates/erp-ai/src/handler/mod.rs b/crates/erp-ai/src/handler/mod.rs index 604d3cf..058480d 100644 --- a/crates/erp-ai/src/handler/mod.rs +++ b/crates/erp-ai/src/handler/mod.rs @@ -558,6 +558,42 @@ where Ok(Json(ApiResponse::ok(result))) } +// === 成本与预算 === + +pub async fn budget_status( + State(state): State, + Extension(ctx): Extension, +) -> Result>, erp_core::error::AppError> +where + AiState: FromRef, + S: Clone + Send + Sync + 'static, +{ + require_permission(&ctx, "ai.usage.list")?; + let cost_svc = crate::service::cost::CostService::new(state.db.clone()); + let status = cost_svc.get_budget_status(ctx.tenant_id).await?; + Ok(Json(ApiResponse::ok(status))) +} + +#[derive(Debug, Deserialize)] +pub struct CostEstimateQuery { + pub analysis_type: String, + pub model: Option, +} + +pub async fn cost_estimate( + Extension(ctx): Extension, + Query(params): Query, +) -> Result>, erp_core::error::AppError> +where + AiState: FromRef, + S: Clone + Send + Sync + 'static, +{ + require_permission(&ctx, "ai.usage.list")?; + let model = params.model.unwrap_or_else(|| "claude-sonnet-4-6".to_string()); + let estimate = crate::service::cost::CostService::estimate_cost(¶ms.analysis_type, &model); + Ok(Json(ApiResponse::ok(estimate))) +} + // === SSE 流构建辅助 === fn build_sse_stream( diff --git a/crates/erp-ai/src/module.rs b/crates/erp-ai/src/module.rs index 373e783..173a64e 100644 --- a/crates/erp-ai/src/module.rs +++ b/crates/erp-ai/src/module.rs @@ -359,5 +359,13 @@ impl AiModule { "/ai/quota/summary", axum::routing::get(crate::handler::quota_summary), ) + .route( + "/ai/budget/status", + axum::routing::get(crate::handler::budget_status), + ) + .route( + "/ai/cost/estimate", + axum::routing::get(crate::handler::cost_estimate), + ) } }