diff --git a/src-tauri/src/database/schema.rs b/src-tauri/src/database/schema.rs index 4f8257cc2..2a9e1ded7 100644 --- a/src-tauri/src/database/schema.rs +++ b/src-tauri/src/database/schema.rs @@ -1128,9 +1128,7 @@ impl Database { WHERE model_id = ?1", rusqlite::params![model_id, input, output, cache_read, cache_creation], ) - .map_err(|e| { - AppError::Database(format!("更新模型 {model_id} 定价失败: {e}")) - })?; + .map_err(|e| AppError::Database(format!("更新模型 {model_id} 定价失败: {e}")))?; } log::info!("v7 -> v8 迁移完成:data_source 列、session_log_sync 表、修正 13 个模型定价"); @@ -1457,7 +1455,14 @@ impl Database { "0", ), // Gemini 2.0 系列 - ("gemini-2.0-flash", "Gemini 2.0 Flash", "0.10", "0.40", "0.025", "0"), + ( + "gemini-2.0-flash", + "Gemini 2.0 Flash", + "0.10", + "0.40", + "0.025", + "0", + ), // StepFun 系列 ( "step-3.5-flash", @@ -1469,17 +1474,66 @@ impl Database { ), // ====== 国产模型 (USD/1M tokens) ====== // Doubao (字节跳动) - ("doubao-seed-code", "Doubao Seed Code", "0.17", "1.11", "0.02", "0"), + ( + "doubao-seed-code", + "Doubao Seed Code", + "0.17", + "1.11", + "0.02", + "0", + ), // DeepSeek 系列 - ("deepseek-v3.2", "DeepSeek V3.2", "0.28", "0.42", "0.028", "0"), - ("deepseek-v3.1", "DeepSeek V3.1", "0.55", "1.67", "0.055", "0"), + ( + "deepseek-v3.2", + "DeepSeek V3.2", + "0.28", + "0.42", + "0.028", + "0", + ), + ( + "deepseek-v3.1", + "DeepSeek V3.1", + "0.55", + "1.67", + "0.055", + "0", + ), ("deepseek-v3", "DeepSeek V3", "0.28", "1.11", "0.028", "0"), - ("deepseek-chat", "DeepSeek Chat", "0.28", "0.42", "0.028", "0"), - ("deepseek-reasoner", "DeepSeek Reasoner", "0.28", "0.42", "0.028", "0"), + ( + "deepseek-chat", + "DeepSeek Chat", + "0.28", + "0.42", + "0.028", + "0", + ), + ( + "deepseek-reasoner", + "DeepSeek Reasoner", + "0.28", + "0.42", + "0.028", + "0", + ), // Kimi (月之暗面) - ("kimi-k2-thinking", "Kimi K2 Thinking", "0.55", "2.20", "0.10", "0"), + ( + "kimi-k2-thinking", + "Kimi K2 Thinking", + "0.55", + "2.20", + "0.10", + "0", + ), ("kimi-k2-0905", "Kimi K2", "0.55", "2.20", "0.10", "0"), - ("kimi-k2-turbo", "Kimi K2 Turbo", "1.11", "8.06", "0.14", "0"), + ( + "kimi-k2-turbo", + "Kimi K2 Turbo", + "1.11", + "8.06", + "0.14", + "0", + ), ("kimi-k2.5", "Kimi K2.5", "0.60", "3.00", "0.10", "0"), // MiniMax 系列 ("minimax-m2.1", "MiniMax M2.1", "0.27", "0.95", "0.03", "0"), @@ -1496,7 +1550,14 @@ impl Database { ("glm-4.7", "GLM-4.7", "0.39", "1.75", "0.04", "0"), ("glm-4.6", "GLM-4.6", "0.28", "1.11", "0.03", "0"), // Mimo (小米) - ("mimo-v2-flash", "Mimo V2 Flash", "0.09", "0.29", "0.009", "0"), + ( + "mimo-v2-flash", + "Mimo V2 Flash", + "0.09", + "0.29", + "0.009", + "0", + ), ]; for (model_id, display_name, input, output, cache_read, cache_creation) in pricing_data { diff --git a/src-tauri/src/services/session_usage.rs b/src-tauri/src/services/session_usage.rs index dd4bbbb92..d821f35d2 100644 --- a/src-tauri/src/services/session_usage.rs +++ b/src-tauri/src/services/session_usage.rs @@ -155,8 +155,8 @@ fn sync_single_file(db: &Database, file_path: &Path) -> Result<(u32, u32), AppEr } // 从上次偏移位置开始增量解析 - let file = fs::File::open(file_path) - .map_err(|e| AppError::Config(format!("无法打开文件: {e}")))?; + let file = + fs::File::open(file_path).map_err(|e| AppError::Config(format!("无法打开文件: {e}")))?; let reader = BufReader::new(file); let mut line_offset: i64 = 0; @@ -272,7 +272,7 @@ fn sync_single_file(db: &Database, file_path: &Path) -> Result<(u32, u32), AppEr let mut imported: u32 = 0; let mut skipped: u32 = 0; - for (_, msg) in &messages { + for msg in messages.values() { // 只导入有 stop_reason 的最终条目(完整的 API 调用) if msg.stop_reason.is_none() { continue; @@ -383,26 +383,26 @@ fn insert_session_log_entry( let pricing = find_model_pricing_for_session(&conn, &msg.model); let multiplier = Decimal::from(1); - let (input_cost, output_cost, cache_read_cost, cache_creation_cost, total_cost) = - match pricing { - Some(p) => { - let cost = CostCalculator::calculate(&usage, &p, multiplier); - ( - cost.input_cost.to_string(), - cost.output_cost.to_string(), - cost.cache_read_cost.to_string(), - cost.cache_creation_cost.to_string(), - cost.total_cost.to_string(), - ) - } - None => ( - "0".to_string(), - "0".to_string(), - "0".to_string(), - "0".to_string(), - "0".to_string(), - ), - }; + let (input_cost, output_cost, cache_read_cost, cache_creation_cost, total_cost) = match pricing + { + Some(p) => { + let cost = CostCalculator::calculate(&usage, &p, multiplier); + ( + cost.input_cost.to_string(), + cost.output_cost.to_string(), + cost.cache_read_cost.to_string(), + cost.cache_creation_cost.to_string(), + cost.total_cost.to_string(), + ) + } + None => ( + "0".to_string(), + "0".to_string(), + "0".to_string(), + "0".to_string(), + "0".to_string(), + ), + }; conn.execute( "INSERT OR IGNORE INTO proxy_request_logs ( diff --git a/src-tauri/src/services/session_usage_codex.rs b/src-tauri/src/services/session_usage_codex.rs index 6d211d30c..13dff44b0 100644 --- a/src-tauri/src/services/session_usage_codex.rs +++ b/src-tauri/src/services/session_usage_codex.rs @@ -746,7 +746,10 @@ mod tests { #[test] fn test_normalize_codex_model_strip_prefix() { assert_eq!(normalize_codex_model("openai/gpt-5.4"), "gpt-5.4"); - assert_eq!(normalize_codex_model("azure/gpt-5.2-codex"), "gpt-5.2-codex"); + assert_eq!( + normalize_codex_model("azure/gpt-5.2-codex"), + "gpt-5.2-codex" + ); assert_eq!(normalize_codex_model("OPENAI/GPT-5.4"), "gpt-5.4"); } @@ -784,10 +787,7 @@ mod tests { "gpt-5.4" ); // prefix + compact date - assert_eq!( - normalize_codex_model("openai/gpt-5.4-20260305"), - "gpt-5.4" - ); + assert_eq!(normalize_codex_model("openai/gpt-5.4-20260305"), "gpt-5.4"); } #[test] @@ -799,7 +799,7 @@ mod tests { output: 50, }); let current = CumulativeTokens { - input: 110, // delta = 10 + input: 110, // delta = 10 cached_input: 80, // delta = 80(异常:大于 input delta) output: 60, }; diff --git a/src-tauri/src/services/session_usage_gemini.rs b/src-tauri/src/services/session_usage_gemini.rs index 1b20c69fa..8bac1fa4f 100644 --- a/src-tauri/src/services/session_usage_gemini.rs +++ b/src-tauri/src/services/session_usage_gemini.rs @@ -182,10 +182,7 @@ fn sync_single_gemini_file(db: &Database, file_path: &Path) -> Result<(u32, u32) gemini_msg_count += 1; // 提取消息 ID 和模型 - let message_id = msg - .get("id") - .and_then(|v| v.as_str()) - .unwrap_or("unknown"); + let message_id = msg.get("id").and_then(|v| v.as_str()).unwrap_or("unknown"); let model = msg .get("model") .and_then(|v| v.as_str()) @@ -222,22 +219,10 @@ fn sync_single_gemini_file(db: &Database, file_path: &Path) -> Result<(u32, u32) /// 从 tokens JSON 对象中提取 token 数据 fn parse_gemini_tokens(tokens: &serde_json::Value) -> GeminiTokens { GeminiTokens { - input: tokens - .get("input") - .and_then(|v| v.as_u64()) - .unwrap_or(0) as u32, - output: tokens - .get("output") - .and_then(|v| v.as_u64()) - .unwrap_or(0) as u32, - cached: tokens - .get("cached") - .and_then(|v| v.as_u64()) - .unwrap_or(0) as u32, - thoughts: tokens - .get("thoughts") - .and_then(|v| v.as_u64()) - .unwrap_or(0) as u32, + input: tokens.get("input").and_then(|v| v.as_u64()).unwrap_or(0) as u32, + output: tokens.get("output").and_then(|v| v.as_u64()).unwrap_or(0) as u32, + cached: tokens.get("cached").and_then(|v| v.as_u64()).unwrap_or(0) as u32, + thoughts: tokens.get("thoughts").and_then(|v| v.as_u64()).unwrap_or(0) as u32, } } @@ -494,7 +479,9 @@ mod tests { assert_eq!(tokens.input, 0); assert_eq!(tokens.output, 0); // 全零(包括 cached=0)会被 sync 逻辑跳过 - assert!(tokens.input == 0 && tokens.output == 0 && tokens.thoughts == 0 && tokens.cached == 0); + assert!( + tokens.input == 0 && tokens.output == 0 && tokens.thoughts == 0 && tokens.cached == 0 + ); } #[test] @@ -509,7 +496,8 @@ mod tests { let tokens = parse_gemini_tokens(&json); assert_eq!(tokens.cached, 5000); // 跳过条件:所有四个字段都为 0 才跳过 - let should_skip = tokens.input == 0 && tokens.output == 0 && tokens.thoughts == 0 && tokens.cached == 0; + let should_skip = + tokens.input == 0 && tokens.output == 0 && tokens.thoughts == 0 && tokens.cached == 0; assert!(!should_skip, "纯缓存命中记录不应被跳过"); } } diff --git a/src-tauri/src/services/skill.rs b/src-tauri/src/services/skill.rs index 461acb002..f94dd8391 100644 --- a/src-tauri/src/services/skill.rs +++ b/src-tauri/src/services/skill.rs @@ -854,6 +854,7 @@ impl SkillService { } /// 递归收集目录下所有非隐藏文件 + #[allow(clippy::only_used_in_recursion)] fn collect_files_for_hash(base: &Path, current: &Path, files: &mut Vec) -> Result<()> { let entries = fs::read_dir(current) .with_context(|| format!("读取目录失败: {}", current.display()))?; @@ -2737,10 +2738,7 @@ impl SkillService { repo_name: repo.clone(), repo_branch: "main".to_string(), installs: s.installs, - readme_url: Some(format!( - "https://github.com/{}/{}", - owner, repo - )), + readme_url: Some(format!("https://github.com/{}/{}", owner, repo)), }) }) .collect(); diff --git a/src-tauri/src/services/usage_stats.rs b/src-tauri/src/services/usage_stats.rs index 278622186..b43ea714a 100644 --- a/src-tauri/src/services/usage_stats.rs +++ b/src-tauri/src/services/usage_stats.rs @@ -383,7 +383,10 @@ impl Database { let mut rstmt = conn.prepare(&rollup_sql)?; let rrows = if let Some(at) = app_type { - rstmt.query_map(params![start_ts, end_ts, bucket_seconds, at], rollup_row_mapper)? + rstmt.query_map( + params![start_ts, end_ts, bucket_seconds, at], + rollup_row_mapper, + )? } else { rstmt.query_map(params![start_ts, end_ts, bucket_seconds], rollup_row_mapper)? }; @@ -757,38 +760,36 @@ impl Database { LEFT JOIN providers p ON l.provider_id = p.id AND l.app_type = p.app_type WHERE l.request_id = ?" ); - let result = conn.query_row( - &detail_sql, - [request_id], - |row| { - Ok(RequestLogDetail { - request_id: row.get(0)?, - provider_id: row.get(1)?, - provider_name: row.get(2)?, - app_type: row.get(3)?, - model: row.get(4)?, - request_model: row.get(5)?, - cost_multiplier: row.get::<_, Option>(6)?.unwrap_or_else(|| "1".to_string()), - input_tokens: row.get::<_, i64>(7)? as u32, - output_tokens: row.get::<_, i64>(8)? as u32, - cache_read_tokens: row.get::<_, i64>(9)? as u32, - cache_creation_tokens: row.get::<_, i64>(10)? as u32, - input_cost_usd: row.get(11)?, - output_cost_usd: row.get(12)?, - cache_read_cost_usd: row.get(13)?, - cache_creation_cost_usd: row.get(14)?, - total_cost_usd: row.get(15)?, - is_streaming: row.get::<_, i64>(16)? != 0, - latency_ms: row.get::<_, i64>(17)? as u64, - first_token_ms: row.get::<_, Option>(18)?.map(|v| v as u64), - duration_ms: row.get::<_, Option>(19)?.map(|v| v as u64), - status_code: row.get::<_, i64>(20)? as u16, - error_message: row.get(21)?, - created_at: row.get(22)?, - data_source: row.get(23)?, - }) - }, - ); + let result = conn.query_row(&detail_sql, [request_id], |row| { + Ok(RequestLogDetail { + request_id: row.get(0)?, + provider_id: row.get(1)?, + provider_name: row.get(2)?, + app_type: row.get(3)?, + model: row.get(4)?, + request_model: row.get(5)?, + cost_multiplier: row + .get::<_, Option>(6)? + .unwrap_or_else(|| "1".to_string()), + input_tokens: row.get::<_, i64>(7)? as u32, + output_tokens: row.get::<_, i64>(8)? as u32, + cache_read_tokens: row.get::<_, i64>(9)? as u32, + cache_creation_tokens: row.get::<_, i64>(10)? as u32, + input_cost_usd: row.get(11)?, + output_cost_usd: row.get(12)?, + cache_read_cost_usd: row.get(13)?, + cache_creation_cost_usd: row.get(14)?, + total_cost_usd: row.get(15)?, + is_streaming: row.get::<_, i64>(16)? != 0, + latency_ms: row.get::<_, i64>(17)? as u64, + first_token_ms: row.get::<_, Option>(18)?.map(|v| v as u64), + duration_ms: row.get::<_, Option>(19)?.map(|v| v as u64), + status_code: row.get::<_, i64>(20)? as u16, + error_message: row.get(21)?, + created_at: row.get(22)?, + data_source: row.get(23)?, + }) + }); match result { Ok(mut detail) => { diff --git a/src/components/UsageScriptModal.tsx b/src/components/UsageScriptModal.tsx index 257a2333c..a0d33b077 100644 --- a/src/components/UsageScriptModal.tsx +++ b/src/components/UsageScriptModal.tsx @@ -1066,7 +1066,7 @@ const UsageScriptModal: React.FC = ({ ...script, timeout: e.target.value === "" - ? (("" as unknown) as number) + ? ("" as unknown as number) : Number(e.target.value), }) } @@ -1098,7 +1098,7 @@ const UsageScriptModal: React.FC = ({ ...script, autoQueryInterval: e.target.value === "" - ? (("" as unknown) as number) + ? ("" as unknown as number) : Number(e.target.value), }) }