Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/api/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand All @@ -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);
}
}
Expand Down
22 changes: 19 additions & 3 deletions src/api/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ impl ApiClient {
Ok(Self { config, client })
}

pub fn get_usage(&self) -> Result<UsageData, Box<dyn std::error::Error>> {
pub fn get_usage(&self, model: Option<&str>) -> Result<UsageData, Box<dyn std::error::Error>> {
let is_packyapi = self.config.is_packyapi();

let response = if is_packyapi {
Expand All @@ -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()?
};

Expand Down Expand Up @@ -61,12 +68,20 @@ impl ApiClient {
Ok(usage)
}

pub fn get_subscriptions(&self) -> Result<Vec<SubscriptionData>, Box<dyn std::error::Error>> {
pub fn get_subscriptions(&self, model: Option<&str>) -> Result<Vec<SubscriptionData>, Box<dyn std::error::Error>> {
// 构建请求体,传入 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() {
Expand Down Expand Up @@ -95,7 +110,8 @@ impl ApiClient {
}

pub fn check_token_limit(&self) -> Result<bool, Box<dyn std::error::Error>> {
let usage = self.get_usage()?;
// 这个方法用于快速检查,没有 model 上下文时传 None
let usage = self.get_usage(None)?;
Ok(usage.get_remaining_tokens() == 0)
}
}
45 changes: 40 additions & 5 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ pub struct Code88UsageData {
#[serde(rename = "currentCredits")]
pub current_credits: f64,

/// 订阅实体列表,包含所有套餐的详细信息
#[serde(rename = "subscriptionEntityList", default)]
pub subscription_entity_list: Vec<SubscriptionEntity>,

#[serde(default)]
pub used_tokens: u64,
#[serde(default)]
Expand All @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
10 changes: 6 additions & 4 deletions src/core/segments/byebyecode_subscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SegmentData> {
pub fn collect(config: &Config, input: &InputData) -> Option<SegmentData> {
// Get API config from segment options
let segment = config
.segments
Expand Down Expand Up @@ -95,17 +95,19 @@ pub fn collect(config: &Config, _input: &InputData) -> Option<SegmentData> {
};

// 实时获取数据,不使用缓存
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<Vec<crate::api::SubscriptionData>> {
fn fetch_subscriptions_sync(api_key: &str, model: Option<&str>) -> Option<Vec<crate::api::SubscriptionData>> {
let api_config = ApiConfig {
enabled: true,
api_key: api_key.to_string(),
..Default::default()
};

let client = ApiClient::new(api_config).ok()?;
let subs = client.get_subscriptions().ok()?;
let subs = client.get_subscriptions(model).ok()?;
Some(subs)
}

Expand Down
36 changes: 26 additions & 10 deletions src/core/segments/byebyecode_usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SegmentData> {
pub fn collect(config: &Config, input: &InputData) -> Option<SegmentData> {
// Get API config from segment options
let segment = config
.segments
Expand Down Expand Up @@ -60,9 +60,11 @@ pub fn collect(config: &Config, _input: &InputData) -> Option<SegmentData> {
.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<crate::api::UsageData> {
fn fetch_usage_sync(api_key: &str, usage_url: &str, model: Option<&str>) -> Option<crate::api::UsageData> {
let api_config = ApiConfig {
enabled: true,
api_key: api_key.to_string(),
Expand All @@ -71,7 +73,7 @@ pub fn collect(config: &Config, _input: &InputData) -> Option<SegmentData> {
};

let client = ApiClient::new(api_config).ok()?;
let usage = client.get_usage().ok()?;
let usage = client.get_usage(model).ok()?;
Some(usage)
}

Expand All @@ -89,8 +91,9 @@ pub fn collect(config: &Config, _input: &InputData) -> Option<SegmentData> {

// 检查额度是否用完(包括超额使用)
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();
Expand Down Expand Up @@ -129,17 +132,30 @@ pub fn collect(config: &Config, _input: &InputData) -> Option<SegmentData> {
});
}

// 正常显示
// 正常显示 - 使用进度条可视化
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,
})
}

fn fetch_subscriptions_sync(
api_key: &str,
subscription_url: &str,
model: Option<&str>,
) -> Option<Vec<crate::api::SubscriptionData>> {
let api_config = ApiConfig {
enabled: true,
Expand All @@ -149,6 +165,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)
}
Loading