From 0a650f858a931ede940894ce0c6732b49f19b380 Mon Sep 17 00:00:00 2001 From: John Ye Date: Wed, 10 Dec 2025 11:49:18 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=A0=8F=E9=94=99=E8=AF=AF=E6=98=BE=E7=A4=BA=20Free=20?= =?UTF-8?q?=E5=A5=97=E9=A4=90=E7=94=A8=E9=87=8F=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=20(#9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:状态栏始终显示 Free 套餐用量($0/$20),即使 Plus 套餐正在被扣费 根因:API 顶层返回 Free 套餐数据,实际数据在 subscriptionEntityList 中 修复: - 添加 SubscriptionEntity 结构体解析订阅列表 - 从列表中找到正在扣费的套餐(currentCredits < creditLimit) - 给 /api/usage 和 /api/subscription 传入 model 参数 修复 #9 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/api/cache.rs | 6 ++- src/api/client.rs | 22 ++++++++-- src/api/mod.rs | 45 +++++++++++++++++--- src/core/segments/byebyecode_subscription.rs | 10 +++-- src/core/segments/byebyecode_usage.rs | 18 +++++--- 5 files changed, 80 insertions(+), 21 deletions(-) diff --git a/src/api/cache.rs b/src/api/cache.rs index c788fa6..8b7356f 100644 --- a/src/api/cache.rs +++ b/src/api/cache.rs @@ -122,7 +122,8 @@ pub fn spawn_background_subscription_update(api_key: String) { }; if let Ok(client) = super::client::ApiClient::new(api_config) { - if let Ok(subs) = client.get_subscriptions() { + // 后台缓存更新时没有 model 信息,传 None + if let Ok(subs) = client.get_subscriptions(None) { let _ = save_cached_subscriptions(&subs); } } @@ -142,7 +143,8 @@ pub fn spawn_background_usage_update(api_key: String) { }; if let Ok(client) = super::client::ApiClient::new(api_config) { - if let Ok(usage) = client.get_usage() { + // 后台缓存更新时没有 model 信息,传 None + if let Ok(usage) = client.get_usage(None) { let _ = save_cached_usage(&usage); } } diff --git a/src/api/client.rs b/src/api/client.rs index ed1ac4f..9c8d8c7 100644 --- a/src/api/client.rs +++ b/src/api/client.rs @@ -17,7 +17,7 @@ impl ApiClient { Ok(Self { config, client }) } - pub fn get_usage(&self) -> Result> { + pub fn get_usage(&self, model: Option<&str>) -> Result> { let is_packyapi = self.config.is_packyapi(); let response = if is_packyapi { @@ -26,10 +26,17 @@ impl ApiClient { .header("Authorization", format!("Bearer {}", self.config.api_key)) .send()? } else { + // 构建请求体,传入 model 参数以获取正确套餐的用量 + // 如果不传 model,API 会默认返回 free 套餐的用量 + let body = match model { + Some(m) => serde_json::json!({ "model": m }), + None => serde_json::json!({}), + }; self.client .post(&self.config.usage_url) .header("Authorization", format!("Bearer {}", self.config.api_key)) .header("Content-Type", "application/json") + .json(&body) .send()? }; @@ -61,12 +68,20 @@ impl ApiClient { Ok(usage) } - pub fn get_subscriptions(&self) -> Result, Box> { + pub fn get_subscriptions(&self, model: Option<&str>) -> Result, Box> { + // 构建请求体,传入 model 参数以获取正确的套餐信息 + // 如果不传 model,API 会默认返回 free 套餐 + let body = match model { + Some(m) => serde_json::json!({ "model": m }), + None => serde_json::json!({}), + }; + let response = self .client .post(&self.config.subscription_url) .header("Authorization", format!("Bearer {}", self.config.api_key)) .header("Content-Type", "application/json") + .json(&body) .send()?; if !response.status().is_success() { @@ -95,7 +110,8 @@ impl ApiClient { } pub fn check_token_limit(&self) -> Result> { - let usage = self.get_usage()?; + // 这个方法用于快速检查,没有 model 上下文时传 None + let usage = self.get_usage(None)?; Ok(usage.get_remaining_tokens() == 0) } } diff --git a/src/api/mod.rs b/src/api/mod.rs index d871e76..2942f89 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -54,6 +54,10 @@ pub struct Code88UsageData { #[serde(rename = "currentCredits")] pub current_credits: f64, + /// 订阅实体列表,包含所有套餐的详细信息 + #[serde(rename = "subscriptionEntityList", default)] + pub subscription_entity_list: Vec, + #[serde(default)] pub used_tokens: u64, #[serde(default)] @@ -62,6 +66,19 @@ pub struct Code88UsageData { pub percentage_used: f64, } +/// 订阅实体(从 subscriptionEntityList 解析) +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct SubscriptionEntity { + #[serde(rename = "subscriptionName")] + pub subscription_name: String, + #[serde(rename = "creditLimit")] + pub credit_limit: f64, + #[serde(rename = "currentCredits")] + pub current_credits: f64, + #[serde(rename = "isActive")] + pub is_active: bool, +} + #[derive(Debug, Clone, Deserialize, Serialize)] pub struct PackyUsageResponse { pub code: bool, @@ -130,20 +147,38 @@ impl UsageData { impl Code88UsageData { pub fn calculate(&mut self) { - let used_credits = self.credit_limit - self.current_credits; - self.percentage_used = if self.credit_limit > 0.0 { - (used_credits / self.credit_limit * 100.0).clamp(0.0, 100.0) + // 优先从 subscriptionEntityList 中找到正在扣费的套餐 + // 判断逻辑:is_active=true 且 currentCredits < creditLimit(已产生消费) + let active_subscription = self + .subscription_entity_list + .iter() + .filter(|s| s.is_active) + .find(|s| s.current_credits < s.credit_limit); + + // 如果找到正在扣费的套餐,用那个套餐的数据 + let (credit_limit, current_credits) = match active_subscription { + Some(sub) => (sub.credit_limit, sub.current_credits), + None => (self.credit_limit, self.current_credits), + }; + + let used_credits = credit_limit - current_credits; + self.percentage_used = if credit_limit > 0.0 { + (used_credits / credit_limit * 100.0).clamp(0.0, 100.0) } else { 0.0 }; self.used_tokens = (used_credits * 100.0).max(0.0) as u64; - if self.current_credits < 0.0 { + if current_credits < 0.0 { self.remaining_tokens = 0; } else { - self.remaining_tokens = (self.current_credits * 100.0) as u64; + self.remaining_tokens = (current_credits * 100.0) as u64; } + + // 更新顶层数据为实际使用的套餐数据 + self.credit_limit = credit_limit; + self.current_credits = current_credits; } pub fn is_exhausted(&self) -> bool { diff --git a/src/core/segments/byebyecode_subscription.rs b/src/core/segments/byebyecode_subscription.rs index 868f6ef..3247c35 100644 --- a/src/core/segments/byebyecode_subscription.rs +++ b/src/core/segments/byebyecode_subscription.rs @@ -33,7 +33,7 @@ fn get_soft_color(text: &str) -> String { /// ANSI 重置代码 const RESET: &str = "\x1b[0m"; -pub fn collect(config: &Config, _input: &InputData) -> Option { +pub fn collect(config: &Config, input: &InputData) -> Option { // Get API config from segment options let segment = config .segments @@ -95,9 +95,11 @@ pub fn collect(config: &Config, _input: &InputData) -> Option { }; // 实时获取数据,不使用缓存 - let subscriptions = fetch_subscriptions_sync(&api_key)?; + // 传入 model 参数以获取正确的套餐信息 + let model_id = &input.model.id; + let subscriptions = fetch_subscriptions_sync(&api_key, Some(model_id))?; - fn fetch_subscriptions_sync(api_key: &str) -> Option> { + fn fetch_subscriptions_sync(api_key: &str, model: Option<&str>) -> Option> { let api_config = ApiConfig { enabled: true, api_key: api_key.to_string(), @@ -105,7 +107,7 @@ pub fn collect(config: &Config, _input: &InputData) -> Option { }; let client = ApiClient::new(api_config).ok()?; - let subs = client.get_subscriptions().ok()?; + let subs = client.get_subscriptions(model).ok()?; Some(subs) } diff --git a/src/core/segments/byebyecode_usage.rs b/src/core/segments/byebyecode_usage.rs index dc6fd3a..214d8d6 100644 --- a/src/core/segments/byebyecode_usage.rs +++ b/src/core/segments/byebyecode_usage.rs @@ -4,7 +4,7 @@ use crate::config::InputData; use crate::core::segments::SegmentData; use std::collections::HashMap; -pub fn collect(config: &Config, _input: &InputData) -> Option { +pub fn collect(config: &Config, input: &InputData) -> Option { // Get API config from segment options let segment = config .segments @@ -60,9 +60,11 @@ pub fn collect(config: &Config, _input: &InputData) -> Option { .map(|s| s.to_string()) .unwrap_or_else(|| "https://www.88code.org/api/subscription".to_string()); - let usage = fetch_usage_sync(&api_key, &usage_url)?; + // 从输入数据获取当前使用的模型 + let model_id = &input.model.id; + let usage = fetch_usage_sync(&api_key, &usage_url, Some(model_id))?; - fn fetch_usage_sync(api_key: &str, usage_url: &str) -> Option { + fn fetch_usage_sync(api_key: &str, usage_url: &str, model: Option<&str>) -> Option { let api_config = ApiConfig { enabled: true, api_key: api_key.to_string(), @@ -71,7 +73,7 @@ pub fn collect(config: &Config, _input: &InputData) -> Option { }; let client = ApiClient::new(api_config).ok()?; - let usage = client.get_usage().ok()?; + let usage = client.get_usage(model).ok()?; Some(usage) } @@ -89,8 +91,9 @@ pub fn collect(config: &Config, _input: &InputData) -> Option { // 检查额度是否用完(包括超额使用) if usage.is_exhausted() { - // 实时获取订阅信息 - let subscriptions = fetch_subscriptions_sync(&api_key, &subscription_url); + // 实时获取订阅信息,传入 model 以获取正确的套餐 + let model_id = &input.model.id; + let subscriptions = fetch_subscriptions_sync(&api_key, &subscription_url, Some(model_id)); if let Some(subs) = subscriptions { let active_subs: Vec<_> = subs.iter().filter(|s| s.is_active).collect(); @@ -140,6 +143,7 @@ pub fn collect(config: &Config, _input: &InputData) -> Option { fn fetch_subscriptions_sync( api_key: &str, subscription_url: &str, + model: Option<&str>, ) -> Option> { let api_config = ApiConfig { enabled: true, @@ -149,6 +153,6 @@ fn fetch_subscriptions_sync( }; let client = ApiClient::new(api_config).ok()?; - let subs = client.get_subscriptions().ok()?; + let subs = client.get_subscriptions(model).ok()?; Some(subs) } From 4c7d1d30b2fd66ee53acf5931106fa8b1f3f38aa Mon Sep 17 00:00:00 2001 From: John Ye Date: Wed, 10 Dec 2025 13:02:22 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E7=94=A8=E8=BF=9B=E5=BA=A6?= =?UTF-8?q?=E6=9D=A1=E5=8F=AF=E8=A7=86=E5=8C=96=E7=94=A8=E9=87=8F=E6=98=BE?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 改进状态栏用量显示: - 用进度条替代冗余���"剩$xx.xx"文字 - 显示格式: $13.86/$50 ▓▓▓░░░░░░░ - 10格进度条,直观展示使用比例 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/core/segments/byebyecode_usage.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/core/segments/byebyecode_usage.rs b/src/core/segments/byebyecode_usage.rs index 214d8d6..b637e0f 100644 --- a/src/core/segments/byebyecode_usage.rs +++ b/src/core/segments/byebyecode_usage.rs @@ -132,10 +132,22 @@ pub fn collect(config: &Config, input: &InputData) -> Option { }); } - // 正常显示 + // 正常显示 - 使用进度条可视化 + let percentage = if total_dollars > 0.0 { + (used_dollars / total_dollars * 100.0).clamp(0.0, 100.0) + } else { + 0.0 + }; + + // 生成进度条(10格) + let bar_length = 10; + let filled = ((percentage / 100.0) * bar_length as f64).round() as usize; + let empty = bar_length - filled; + let progress_bar = format!("{}{}", "▓".repeat(filled), "░".repeat(empty)); + Some(SegmentData { - primary: format!("${:.2}/${:.0}", used_dollars, total_dollars), - secondary: format!("剩${:.2}", remaining_dollars), + primary: format!("${:.2}/${:.0} {}", used_dollars, total_dollars, progress_bar), + secondary: String::new(), metadata, }) }