//! 使用统计相关命令 use crate::error::AppError; use crate::services::usage_stats::*; use crate::store::AppState; use tauri::State; /// 获取使用量汇总 #[tauri::command] pub fn get_usage_summary( state: State<'_, AppState>, start_date: Option, end_date: Option, app_type: Option, ) -> Result { state .db .get_usage_summary(start_date, end_date, app_type.as_deref()) } /// 获取每日趋势 #[tauri::command] pub fn get_usage_trends( state: State<'_, AppState>, start_date: Option, end_date: Option, app_type: Option, ) -> Result, AppError> { state .db .get_daily_trends(start_date, end_date, app_type.as_deref()) } /// 获取 Provider 统计 #[tauri::command] pub fn get_provider_stats( state: State<'_, AppState>, start_date: Option, end_date: Option, app_type: Option, ) -> Result, AppError> { state .db .get_provider_stats(start_date, end_date, app_type.as_deref()) } /// 获取模型统计 #[tauri::command] pub fn get_model_stats( state: State<'_, AppState>, start_date: Option, end_date: Option, app_type: Option, ) -> Result, AppError> { state .db .get_model_stats(start_date, end_date, app_type.as_deref()) } /// 获取请求日志列表 #[tauri::command] pub fn get_request_logs( state: State<'_, AppState>, filters: LogFilters, page: u32, page_size: u32, ) -> Result { state.db.get_request_logs(&filters, page, page_size) } /// 获取单个请求详情 #[tauri::command] pub fn get_request_detail( state: State<'_, AppState>, request_id: String, ) -> Result, AppError> { state.db.get_request_detail(&request_id) } /// 获取模型定价列表 #[tauri::command] 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", )?; let rows = stmt.query_map([], |row| { Ok(ModelPricingInfo { model_id: row.get(0)?, display_name: row.get(1)?, input_cost_per_million: row.get(2)?, output_cost_per_million: row.get(3)?, cache_read_cost_per_million: row.get(4)?, cache_creation_cost_per_million: row.get(5)?, }) })?; let mut pricing = Vec::new(); for row in rows { pricing.push(row?); } log::info!("成功获取 {} 条模型定价数据", pricing.len()); Ok(pricing) } /// 更新模型定价 #[tauri::command] pub fn update_model_pricing( state: State<'_, AppState>, model_id: String, display_name: String, input_cost: String, output_cost: String, 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( "INSERT OR REPLACE INTO model_pricing ( model_id, display_name, input_cost_per_million, output_cost_per_million, cache_read_cost_per_million, cache_creation_cost_per_million ) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", rusqlite::params![ model_id, display_name, input_cost, output_cost, cache_read_cost, cache_creation_cost ], ) .map_err(|e| AppError::Database(format!("更新模型定价失败: {e}")))?; Ok(()) } /// 检查 Provider 使用限额 #[tauri::command] pub fn check_provider_limits( state: State<'_, AppState>, provider_id: String, app_type: String, ) -> Result { 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(()) } /// 手动触发会话日志同步 #[tauri::command] pub fn sync_session_usage( state: State<'_, AppState>, ) -> Result { // 同步 Claude 会话日志 let mut result = crate::services::session_usage::sync_claude_session_logs(&state.db)?; // 同步 Codex 使用数据 match crate::services::session_usage_codex::sync_codex_usage(&state.db) { Ok(codex_result) => { result.imported += codex_result.imported; result.skipped += codex_result.skipped; result.files_scanned += codex_result.files_scanned; result.errors.extend(codex_result.errors); } Err(e) => { result.errors.push(format!("Codex 同步失败: {e}")); } } // 同步 Gemini 使用数据 match crate::services::session_usage_gemini::sync_gemini_usage(&state.db) { Ok(gemini_result) => { result.imported += gemini_result.imported; result.skipped += gemini_result.skipped; result.files_scanned += gemini_result.files_scanned; result.errors.extend(gemini_result.errors); } Err(e) => { result.errors.push(format!("Gemini 同步失败: {e}")); } } Ok(result) } /// 获取数据来源分布 #[tauri::command] pub fn get_usage_data_sources( state: State<'_, AppState>, ) -> Result, AppError> { crate::services::session_usage::get_data_source_breakdown(&state.db) } /// 模型定价信息 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct ModelPricingInfo { pub model_id: String, pub display_name: String, pub input_cost_per_million: String, pub output_cost_per_million: String, pub cache_read_cost_per_million: String, pub cache_creation_cost_per_million: String, }