diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 536c77d4b..739300016 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -12,6 +12,7 @@ mod provider; mod proxy; mod settings; pub mod skill; +mod usage; pub use config::*; pub use deeplink::*; @@ -25,3 +26,4 @@ pub use provider::*; pub use proxy::*; pub use settings::*; pub use skill::*; +pub use usage::*; diff --git a/src-tauri/src/commands/usage.rs b/src-tauri/src/commands/usage.rs index 4b2329926..349496931 100644 --- a/src-tauri/src/commands/usage.rs +++ b/src-tauri/src/commands/usage.rs @@ -1,82 +1,89 @@ //! 使用统计相关命令 -use crate::database::Database; use crate::error::AppError; use crate::services::usage_stats::*; +use crate::store::AppState; use tauri::State; /// 获取使用量汇总 #[tauri::command] pub fn get_usage_summary( - db: State<'_, Database>, + state: State<'_, AppState>, start_date: Option, end_date: Option, ) -> Result { - db.get_usage_summary(start_date, end_date) + state.db.get_usage_summary(start_date, end_date) } /// 获取每日趋势 #[tauri::command] pub fn get_usage_trends( - db: State<'_, Database>, + state: State<'_, AppState>, days: u32, ) -> Result, AppError> { - db.get_daily_trends(days) + state.db.get_daily_trends(days) } /// 获取 Provider 统计 #[tauri::command] -pub fn get_provider_stats(db: State<'_, Database>) -> Result, AppError> { - db.get_provider_stats() +pub fn get_provider_stats(state: State<'_, AppState>) -> Result, AppError> { + state.db.get_provider_stats() } /// 获取模型统计 #[tauri::command] -pub fn get_model_stats(db: State<'_, Database>) -> Result, AppError> { - db.get_model_stats() +pub fn get_model_stats(state: State<'_, AppState>) -> Result, AppError> { + state.db.get_model_stats() } /// 获取请求日志列表 #[tauri::command] pub fn get_request_logs( - db: State<'_, Database>, - provider_id: Option, - model: Option, - status_code: Option, - start_date: Option, - end_date: Option, + state: State<'_, AppState>, + filters: LogFilters, limit: u32, offset: u32, ) -> Result, AppError> { - let filters = LogFilters { - provider_id, - model, - status_code, - start_date, - end_date, - }; - db.get_request_logs(&filters, limit, offset) + state.db.get_request_logs(&filters, limit, offset) } /// 获取单个请求详情 #[tauri::command] pub fn get_request_detail( - db: State<'_, Database>, + state: State<'_, AppState>, request_id: String, ) -> Result, AppError> { - db.get_request_detail(&request_id) + state.db.get_request_detail(&request_id) } /// 获取模型定价列表 #[tauri::command] -pub fn get_model_pricing(db: State<'_, Database>) -> Result, AppError> { +pub fn get_model_pricing(state: State<'_, AppState>) -> Result, AppError> { + log::info!("获取模型定价列表"); + state.db.ensure_model_pricing_seeded()?; + + let db = state.db.clone(); let conn = crate::database::lock_conn!(db.conn); - + + // 检查表是否存在 + let table_exists: bool = conn + .query_row( + "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='model_pricing'", + [], + |row| row.get::<_, i64>(0).map(|count| count > 0), + ) + .unwrap_or(false); + + if !table_exists { + log::error!("model_pricing 表不存在,可能需要重启应用以触发数据库迁移"); + return Ok(Vec::new()); + } + let mut stmt = conn.prepare( "SELECT model_id, display_name, input_cost_per_million, output_cost_per_million, cache_read_cost_per_million, cache_creation_cost_per_million FROM model_pricing - ORDER BY display_name" + ORDER BY display_name", )?; let rows = stmt.query_map([], |row| { @@ -95,13 +102,14 @@ pub fn get_model_pricing(db: State<'_, Database>) -> Result, + state: State<'_, AppState>, model_id: String, display_name: String, input_cost: String, @@ -109,6 +117,7 @@ pub fn update_model_pricing( cache_read_cost: String, cache_creation_cost: String, ) -> Result<(), AppError> { + let db = state.db.clone(); let conn = crate::database::lock_conn!(db.conn); conn.execute( @@ -133,15 +142,32 @@ pub fn update_model_pricing( /// 检查 Provider 使用限额 #[tauri::command] pub fn check_provider_limits( - db: State<'_, Database>, + state: State<'_, AppState>, provider_id: String, app_type: String, ) -> Result { - db.check_provider_limits(&provider_id, &app_type) + state.db.check_provider_limits(&provider_id, &app_type) +} + +/// 删除模型定价 +#[tauri::command] +pub fn delete_model_pricing(state: State<'_, AppState>, model_id: String) -> Result<(), AppError> { + let db = state.db.clone(); + let conn = crate::database::lock_conn!(db.conn); + + conn.execute( + "DELETE FROM model_pricing WHERE model_id = ?1", + rusqlite::params![model_id], + ) + .map_err(|e| AppError::Database(format!("删除模型定价失败: {e}")))?; + + log::info!("已删除模型定价: {model_id}"); + Ok(()) } /// 模型定价信息 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] pub struct ModelPricingInfo { pub model_id: String, pub display_name: String, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 106b2cb35..d46a75d84 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -650,6 +650,17 @@ pub fn run() { commands::get_proxy_config, commands::update_proxy_config, commands::is_proxy_running, + // Usage statistics + commands::get_usage_summary, + commands::get_usage_trends, + commands::get_provider_stats, + commands::get_model_stats, + commands::get_request_logs, + commands::get_request_detail, + commands::get_model_pricing, + commands::update_model_pricing, + commands::delete_model_pricing, + commands::check_provider_limits, ]); let app = builder diff --git a/src-tauri/src/services/mod.rs b/src-tauri/src/services/mod.rs index a861113ae..efa57d254 100644 --- a/src-tauri/src/services/mod.rs +++ b/src-tauri/src/services/mod.rs @@ -7,6 +7,7 @@ pub mod provider; pub mod proxy; pub mod skill; pub mod speedtest; +pub mod usage_stats; pub use config::ConfigService; pub use mcp::McpService; @@ -15,3 +16,8 @@ pub use provider::{ProviderService, ProviderSortUpdate}; pub use proxy::ProxyService; pub use skill::{Skill, SkillRepo, SkillService}; pub use speedtest::{EndpointLatency, SpeedtestService}; +#[allow(unused_imports)] +pub use usage_stats::{ + DailyStats, LogFilters, ModelStats, ProviderLimitStatus, ProviderStats, RequestLogDetail, + UsageSummary, +};