修改计算规则以及数据库

This commit is contained in:
2025-08-10 02:46:52 +08:00
parent dcd6b42a66
commit d8995bfe36
6 changed files with 733 additions and 102 deletions

View File

@@ -8,97 +8,98 @@ use tauri::command;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct UsageEntry {
timestamp: String,
model: String,
input_tokens: u64,
output_tokens: u64,
cache_creation_tokens: u64,
cache_read_tokens: u64,
cost: f64,
session_id: String,
project_path: String,
pub timestamp: String,
pub model: String,
pub input_tokens: u64,
pub output_tokens: u64,
pub cache_creation_tokens: u64,
pub cache_read_tokens: u64,
pub cost: f64,
pub session_id: String,
pub project_path: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct UsageStats {
total_cost: f64,
total_tokens: u64,
total_input_tokens: u64,
total_output_tokens: u64,
total_cache_creation_tokens: u64,
total_cache_read_tokens: u64,
total_sessions: u64,
by_model: Vec<ModelUsage>,
by_date: Vec<DailyUsage>,
by_project: Vec<ProjectUsage>,
pub total_cost: f64,
pub total_tokens: u64,
pub total_input_tokens: u64,
pub total_output_tokens: u64,
pub total_cache_creation_tokens: u64,
pub total_cache_read_tokens: u64,
pub total_sessions: u64,
pub by_model: Vec<ModelUsage>,
pub by_date: Vec<DailyUsage>,
pub by_project: Vec<ProjectUsage>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ModelUsage {
model: String,
total_cost: f64,
total_tokens: u64,
input_tokens: u64,
output_tokens: u64,
cache_creation_tokens: u64,
cache_read_tokens: u64,
session_count: u64,
pub model: String,
pub total_cost: f64,
pub total_tokens: u64,
pub input_tokens: u64,
pub output_tokens: u64,
pub cache_creation_tokens: u64,
pub cache_read_tokens: u64,
pub session_count: u64,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DailyUsage {
date: String,
total_cost: f64,
total_tokens: u64,
pub date: String,
pub total_cost: f64,
pub total_tokens: u64,
// New detailed per-day breakdowns
input_tokens: u64,
output_tokens: u64,
cache_creation_tokens: u64,
cache_read_tokens: u64,
request_count: u64,
models_used: Vec<String>,
pub input_tokens: u64,
pub output_tokens: u64,
pub cache_creation_tokens: u64,
pub cache_read_tokens: u64,
pub request_count: u64,
pub models_used: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ProjectUsage {
project_path: String,
project_name: String,
total_cost: f64,
total_tokens: u64,
session_count: u64,
last_used: String,
pub project_path: String,
pub project_name: String,
pub total_cost: f64,
pub total_tokens: u64,
pub session_count: u64,
pub last_used: String,
}
// Claude pricing constants (per million tokens)
// Claude 4 系列
const OPUS_4_INPUT_PRICE: f64 = 15.0;
const OPUS_4_OUTPUT_PRICE: f64 = 75.0;
const OPUS_4_CACHE_WRITE_PRICE: f64 = 18.75;
const OPUS_4_CACHE_READ_PRICE: f64 = 1.20; // 修正为 1.20
// 最新价格表 (2025-01)
// Claude 4.x 系列
const OPUS_4_1_INPUT_PRICE: f64 = 15.0; // Opus 4.1
const OPUS_4_1_OUTPUT_PRICE: f64 = 75.0;
const OPUS_4_1_CACHE_WRITE_PRICE: f64 = 18.75;
const OPUS_4_1_CACHE_READ_PRICE: f64 = 1.50; // 更新为 1.50
const SONNET_4_INPUT_PRICE: f64 = 3.0;
const SONNET_4_INPUT_PRICE: f64 = 3.0; // Sonnet 4
const SONNET_4_OUTPUT_PRICE: f64 = 15.0;
const SONNET_4_CACHE_WRITE_PRICE: f64 = 3.75;
const SONNET_4_CACHE_READ_PRICE: f64 = 0.30;
// Claude 3.x 系列
// Sonnet 3.7/3.5
// Claude 3.x 系列 (旧版本,价格可能不同)
// Sonnet 3.7/3.5 - 假设与 Sonnet 4 相同
const SONNET_3_INPUT_PRICE: f64 = 3.0;
const SONNET_3_OUTPUT_PRICE: f64 = 15.0;
const SONNET_3_CACHE_WRITE_PRICE: f64 = 3.75;
const SONNET_3_CACHE_READ_PRICE: f64 = 0.30;
// Opus 3
// Opus 3 - 假设与 Opus 4.1 相同
const OPUS_3_INPUT_PRICE: f64 = 15.0;
const OPUS_3_OUTPUT_PRICE: f64 = 75.0;
const OPUS_3_CACHE_WRITE_PRICE: f64 = 18.75;
const OPUS_3_CACHE_READ_PRICE: f64 = 1.20;
const OPUS_3_CACHE_READ_PRICE: f64 = 1.50;
// Haiku 3.5
const HAIKU_3_INPUT_PRICE: f64 = 0.80;
const HAIKU_3_OUTPUT_PRICE: f64 = 4.0;
const HAIKU_3_CACHE_WRITE_PRICE: f64 = 1.0;
const HAIKU_3_CACHE_READ_PRICE: f64 = 0.08;
// Haiku 3.5 - 最具性价比
const HAIKU_3_5_INPUT_PRICE: f64 = 0.80;
const HAIKU_3_5_OUTPUT_PRICE: f64 = 4.0;
const HAIKU_3_5_CACHE_WRITE_PRICE: f64 = 1.0;
const HAIKU_3_5_CACHE_READ_PRICE: f64 = 0.08;
#[derive(Debug, Deserialize)]
struct JsonlEntry {
@@ -147,40 +148,47 @@ fn calculate_cost(model: &str, usage: &UsageData) -> f64 {
cost
}
// 独立的模型价格匹配函数,更灵活的模型识别
// 独立的模型价格匹配函数,更精确的模型识别
fn match_model_prices(model_lower: &str) -> (f64, f64, f64, f64) {
// Claude 4 系列
if model_lower.contains("opus") && (model_lower.contains("4") || model_lower.contains("4.")) {
(OPUS_4_INPUT_PRICE, OPUS_4_OUTPUT_PRICE, OPUS_4_CACHE_WRITE_PRICE, OPUS_4_CACHE_READ_PRICE)
} else if model_lower.contains("sonnet") && (model_lower.contains("4") || model_lower.contains("4.")) {
// Claude Opus 4.1 (最新最强)
if model_lower.contains("opus") && (model_lower.contains("4-1") || model_lower.contains("4.1")) {
(OPUS_4_1_INPUT_PRICE, OPUS_4_1_OUTPUT_PRICE, OPUS_4_1_CACHE_WRITE_PRICE, OPUS_4_1_CACHE_READ_PRICE)
}
// Claude Sonnet 4
else if model_lower.contains("sonnet") && (model_lower.contains("-4-") || model_lower.contains("sonnet-4")) {
(SONNET_4_INPUT_PRICE, SONNET_4_OUTPUT_PRICE, SONNET_4_CACHE_WRITE_PRICE, SONNET_4_CACHE_READ_PRICE)
}
// Claude Haiku 3.5
else if model_lower.contains("haiku") {
(HAIKU_3_5_INPUT_PRICE, HAIKU_3_5_OUTPUT_PRICE, HAIKU_3_5_CACHE_WRITE_PRICE, HAIKU_3_5_CACHE_READ_PRICE)
}
// Claude 3.x Sonnet 系列3.7, 3.5
else if model_lower.contains("sonnet") &&
(model_lower.contains("3.7") || model_lower.contains("3.5") || model_lower.contains("3-5")) {
(model_lower.contains("3-7") || model_lower.contains("3.7") ||
model_lower.contains("3-5") || model_lower.contains("3.5")) {
(SONNET_3_INPUT_PRICE, SONNET_3_OUTPUT_PRICE, SONNET_3_CACHE_WRITE_PRICE, SONNET_3_CACHE_READ_PRICE)
}
// Claude 3 Opus
else if model_lower.contains("opus") &&
(model_lower.contains("3") || (!model_lower.contains("4") && !model_lower.contains("4."))) {
// Claude 3 Opus (旧版)
else if model_lower.contains("opus") && model_lower.contains("3") {
(OPUS_3_INPUT_PRICE, OPUS_3_OUTPUT_PRICE, OPUS_3_CACHE_WRITE_PRICE, OPUS_3_CACHE_READ_PRICE)
}
// Claude 3.5 Haiku
else if model_lower.contains("haiku") {
(HAIKU_3_INPUT_PRICE, HAIKU_3_OUTPUT_PRICE, HAIKU_3_CACHE_WRITE_PRICE, HAIKU_3_CACHE_READ_PRICE)
}
// 默认 Sonnet通用后备
// 默认 Sonnet未明确版本号时
else if model_lower.contains("sonnet") {
(SONNET_3_INPUT_PRICE, SONNET_3_OUTPUT_PRICE, SONNET_3_CACHE_WRITE_PRICE, SONNET_3_CACHE_READ_PRICE)
}
// 默认 Opus未明确版本号时假设是最新版
else if model_lower.contains("opus") {
(OPUS_4_1_INPUT_PRICE, OPUS_4_1_OUTPUT_PRICE, OPUS_4_1_CACHE_WRITE_PRICE, OPUS_4_1_CACHE_READ_PRICE)
}
// 未知模型
else {
log::warn!("Unknown model for cost calculation: {}", model_lower);
(0.0, 0.0, 0.0, 0.0)
// 默认使用 Sonnet 3 的价格(保守估计)
(SONNET_3_INPUT_PRICE, SONNET_3_OUTPUT_PRICE, SONNET_3_CACHE_WRITE_PRICE, SONNET_3_CACHE_READ_PRICE)
}
}
fn parse_jsonl_file(
pub fn parse_jsonl_file(
path: &PathBuf,
encoded_project_name: &str,
processed_hashes: &mut HashSet<String>,
@@ -264,13 +272,25 @@ fn parse_jsonl_file(
continue;
}
let cost = entry.cost_usd.unwrap_or_else(|| {
if let Some(model_str) = &message.model {
calculate_cost(model_str, usage)
} else {
0.0
// 始终重新计算成本不信任JSONL中的costUSD字段
// 因为可能存在价格变化或计算错误
let cost = if let Some(model_str) = &message.model {
calculate_cost(model_str, usage)
} else {
0.0
};
// 如果JSONL中有成本可以记录差异用于调试
if let Some(jsonl_cost) = entry.cost_usd {
if (jsonl_cost - cost).abs() > 0.0001 {
log::debug!(
"Cost difference for model {}: JSONL={:.4}, Calculated={:.4}",
message.model.as_ref().unwrap_or(&"unknown".to_string()),
jsonl_cost,
cost
);
}
});
}
// Use actual project path if found, otherwise use encoded name
let project_path = actual_project_path
@@ -324,7 +344,7 @@ fn get_earliest_timestamp(path: &PathBuf) -> Option<String> {
None
}
fn get_all_usage_entries(claude_path: &PathBuf) -> Vec<UsageEntry> {
pub fn get_all_usage_entries(claude_path: &PathBuf) -> Vec<UsageEntry> {
let mut all_entries = Vec::new();
let mut processed_hashes = HashSet::new();
let projects_dir = claude_path.join("projects");