mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
fix(tray): render Codex free-plan 30-day window in tray menu
The tray keeps its own tier-name whitelist (TIER_LABEL_GROUPS) independent
of the frontend TIER_I18N_KEYS. Its month group only listed "monthly", so a
free Codex account whose only tier is "30_day" produced empty labeled parts
→ format_subscription_summary returned None → the tray showed no quota, even
though the footer now does. That breaks the invariant the existing
gemini_summary_lite_only_still_renders test guards: any tier visible in the
footer must not leave the tray blank.
- Add TIER_THIRTY_DAY ("30_day") constant so the string is single-sourced
across backend mapping, tray grouping, and the frontend whitelist
- Map 2_592_000s explicitly to it in window_seconds_to_tier_name
- Add TIER_THIRTY_DAY to the tray month ("m") group
- Add codex_summary_thirty_day_only_still_renders regression test
Follow-up to the review on #3651 / PR for the 30-day footer fix.
This commit is contained in:
@@ -312,6 +312,12 @@ pub const TIER_WEEKLY_LIMIT: &str = "weekly_limit";
|
||||
/// 映射到 `subscription.monthly`。
|
||||
pub const TIER_MONTHLY: &str = "monthly";
|
||||
|
||||
/// Codex 免费方案的 30 天(月)滚动窗口 tier 名。付费方案的次要窗口是 7 天
|
||||
/// (`seven_day`),免费方案则是 30 天。由 `window_seconds_to_tier_name` 产出、
|
||||
/// tray 的月分组渲染、前端 `TIER_I18N_KEYS` 映射到 `subscription.thirtyDay`
|
||||
/// 三处共用同一标识。见 #3651。
|
||||
pub const TIER_THIRTY_DAY: &str = "30_day";
|
||||
|
||||
/// Gemini 用量分组名称(按模型而非时间窗口)。`classify_gemini_model` 输出。
|
||||
pub const TIER_GEMINI_PRO: &str = "gemini_pro";
|
||||
pub const TIER_GEMINI_FLASH: &str = "gemini_flash";
|
||||
@@ -632,8 +638,12 @@ struct CodexUsageResponse {
|
||||
/// 根据窗口秒数映射到 tier 名称(与 Claude 的命名兼容以复用前端 i18n)
|
||||
fn window_seconds_to_tier_name(secs: i64) -> String {
|
||||
match secs {
|
||||
18000 => "five_hour".to_string(),
|
||||
604800 => "seven_day".to_string(),
|
||||
18000 => TIER_FIVE_HOUR.to_string(),
|
||||
604800 => TIER_SEVEN_DAY.to_string(),
|
||||
// Codex 免费方案的 30 天窗口。显式映射到常量,与 tray 月分组、前端
|
||||
// TIER_I18N_KEYS 保持同一标识(否则动态回退虽也得到 "30_day",但字符串
|
||||
// 分散在多处、易和托盘/前端白名单脱节)。见 #3651。
|
||||
2_592_000 => TIER_THIRTY_DAY.to_string(),
|
||||
s => {
|
||||
let hours = s / 3600;
|
||||
if hours >= 24 {
|
||||
@@ -1348,11 +1358,11 @@ mod tests {
|
||||
#[test]
|
||||
fn window_seconds_map_to_expected_tier_names() {
|
||||
// 官方特例窗口
|
||||
assert_eq!(window_seconds_to_tier_name(18000), "five_hour");
|
||||
assert_eq!(window_seconds_to_tier_name(604800), "seven_day");
|
||||
assert_eq!(window_seconds_to_tier_name(18000), TIER_FIVE_HOUR);
|
||||
assert_eq!(window_seconds_to_tier_name(604800), TIER_SEVEN_DAY);
|
||||
// Codex 免费方案的次要窗口是 30 天(30 * 24 * 3600 = 2_592_000 秒)。
|
||||
// 前端 TIER_I18N_KEYS 需要有对应的 "30_day" 才能展示,见 #3651。
|
||||
assert_eq!(window_seconds_to_tier_name(2_592_000), "30_day");
|
||||
// 前端 TIER_I18N_KEYS 与 tray 月分组都需要认得 "30_day",见 #3651。
|
||||
assert_eq!(window_seconds_to_tier_name(2_592_000), TIER_THIRTY_DAY);
|
||||
// 其他窗口按小时/天回退命名
|
||||
assert_eq!(window_seconds_to_tier_name(3600), "1_hour");
|
||||
assert_eq!(window_seconds_to_tier_name(86400), "1_day");
|
||||
|
||||
+18
-3
@@ -19,8 +19,13 @@ const W_TIER_NAMES: &[&str] = &[
|
||||
crate::services::subscription::TIER_SEVEN_DAY_OPUS,
|
||||
crate::services::subscription::TIER_SEVEN_DAY_SONNET,
|
||||
];
|
||||
// 火山方舟 Agent/Coding Plan 的月窗口(5h/周/月 三档)。
|
||||
const M_TIER_NAMES: &[&str] = &[crate::services::subscription::TIER_MONTHLY];
|
||||
// 月窗口分组:火山方舟 Agent/Coding Plan 的月窗口(5h/周/月 三档),
|
||||
// 以及 Codex 免费方案的 30 天窗口(#3651)——两者都归入 "m" 档,避免免费
|
||||
// Codex 账号在托盘里空白(前端 footer 能看到、托盘却不显示的不对称)。
|
||||
const M_TIER_NAMES: &[&str] = &[
|
||||
crate::services::subscription::TIER_MONTHLY,
|
||||
crate::services::subscription::TIER_THIRTY_DAY,
|
||||
];
|
||||
const GEMINI_PRO_TIER_NAMES: &[&str] = &[crate::services::subscription::TIER_GEMINI_PRO];
|
||||
const GEMINI_FLASH_TIER_NAMES: &[&str] = &[crate::services::subscription::TIER_GEMINI_FLASH];
|
||||
const GEMINI_FLASH_LITE_TIER_NAMES: &[&str] =
|
||||
@@ -882,7 +887,7 @@ mod tests {
|
||||
use crate::services::subscription::{
|
||||
CredentialStatus, QuotaTier, SubscriptionQuota, TIER_FIVE_HOUR, TIER_GEMINI_FLASH,
|
||||
TIER_GEMINI_FLASH_LITE, TIER_GEMINI_PRO, TIER_MONTHLY, TIER_SEVEN_DAY, TIER_SEVEN_DAY_OPUS,
|
||||
TIER_SEVEN_DAY_SONNET, TIER_WEEKLY_LIMIT,
|
||||
TIER_SEVEN_DAY_SONNET, TIER_THIRTY_DAY, TIER_WEEKLY_LIMIT,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -964,6 +969,16 @@ mod tests {
|
||||
assert!(s.contains("l80%"), "expected l80% in {s}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_summary_thirty_day_only_still_renders() {
|
||||
// Codex 免费方案的唯一 tier 是 30 天窗口。前端 footer 已能显示(TIER_I18N_KEYS
|
||||
// 有 "30_day"),托盘也必须能显示——否则就是这条不变量要防的非对称:footer
|
||||
// 能看到、托盘却空白。30_day 归入 "m" 月分组。见 #3651。
|
||||
let quota = make_quota("codex", true, vec![tier(TIER_THIRTY_DAY, 85.0)]);
|
||||
let s = format_subscription_summary("a).expect("should format");
|
||||
assert!(s.contains("m85%"), "expected m85% in {s}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gemini_summary_emoji_reflects_highest_tier_including_lite() {
|
||||
// lite 是利用率最高的那条 → emoji 必须是红色,不能被 pro/flash 掩盖。
|
||||
|
||||
Reference in New Issue
Block a user