feat(tray): show cached provider usage in the system tray menu#2184
Conversation
Introduce an in-memory UsageCache on AppState that the existing usage query commands populate on success. The cache is read-only to the rest of the app today; the next commit consumes it from the tray menu. - New services::usage_cache module with split maps: subscription keyed by AppType, script keyed by (AppType, provider_id). - AppType gains Eq + Hash so it can be used as a HashMap key. - commands::subscription::get_subscription_quota now takes State<AppState> and writes through on success (signature change is invisible to the frontend — Tauri injects State automatically). - commands::provider::queryProviderUsage body extracted into an inner async fn; the public command wraps it with write-through, covering Copilot, coding-plan, balance, and generic script paths uniformly. Cache is in-memory only; auto-query interval and the upcoming tray refresh action rebuild it after restarts.
Read UsageCache populated by the previous commit and render it in three places, scoped to whatever TRAY_SECTIONS covers (Claude/Codex/Gemini): 1. Inline suffix on each provider submenu item "AnyProvider · 🟢 5h 18% / 7d 23%" 2. Disabled summary row per visible app under "Show Main" "Claude · Anthropic Official · 🟢 5h 18% / 7d 23%" 3. "Refresh all usage" menu item that triggers get_subscription_quota + queryProviderUsage for every applicable provider, then rebuilds the tray menu via the existing refresh_tray_menu path. Color encoding uses emoji (🟢 <70% / 🟠 70-89% / 🔴 ≥90%) since Tauri 2 tray labels are plain text. Missing cache entry leaves the label unchanged — tray never issues network requests when opened. Three new i18n-ready strings live in TrayTexts (en/zh/ja), following the existing pattern for tray text. Closes farion1231#2178.
d2502dc to
7fc5751
Compare
Why: tray hover triggers backend-only refresh that wrote to UsageCache but never notified the frontend, leaving main UI stale while tray showed fresh numbers. Emit a payload-carrying event after each cache write so React Query can setQueryData directly, keeping both views in sync without duplicate fetches.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4955703e67
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…script cache Address P2 findings from automated review on farion1231#2184: 1. refresh_all_usage_in_tray now filters TRAY_SECTIONS by settings.visible_apps before scheduling subscription/script queries, matching create_tray_menu and preventing wasted external API calls (and rate-limit/auth-error log noise) for apps the user has hidden. 2. format_usage_suffix only trusts the script cache when provider.meta.usage_script is still enabled; when a script is disabled/removed the cached suffix is now invalidated so the tray label no longer shows stale data indefinitely.
|
Addressed both P2 findings from the Codex review in 54f61a2:
|
|
To use Codex here, create a Codex account and connect to github. |
…script cache Address P2 findings from automated review on farion1231#2184: 1. refresh_all_usage_in_tray now filters TRAY_SECTIONS by settings.visible_apps before scheduling subscription/script queries, matching create_tray_menu and preventing wasted external API calls (and rate-limit/auth-error log noise) for apps the user has hidden. 2. format_usage_suffix only trusts the script cache when provider.meta.usage_script is still enabled; when a script is disabled/removed the cached suffix is now invalidated so the tray label no longer shows stale data indefinitely.
54f61a2 to
dab739d
Compare
|
Note: squashed a small internal cleanup into the same commit; the earlier reference |
…play # Conflicts: # src-tauri/src/lib.rs
- Add hermes field to SkillApps mock in useImportSkillsFromApps test - Replace manual match-Ok-Some with .ok() in gemini.rs to satisfy clippy
- Add Provider::is_codex_oauth() and Provider::codex_fast_mode_enabled() to eliminate duplicated meta extraction in claude.rs and stream_check.rs - Fix non-codex-oauth tests to pass codex_fast_mode=false (was true, harmless but semantically misleading) - Remove redundant is_dir() guard after resolve_skill_source_dir already guarantees the returned path is a directory
|
已同步 upstream/main,顺带修了两个 CI 问题: 这个 tray 用量显示这两天自己在用,感觉还挺方便的。唯一的遗憾是想把配额到期时间也放上去,但一直找不到比较优雅的展示方式(label 太长了不好看)。不知道大佬有没有什么思路,欢迎指点 🙏 |
…-lite Follow-up to the tray usage-display feature addressing review feedback: - Write snapshots for both Ok(success:false) and Err paths in queryProviderUsage / get_subscription_quota so stale success data no longer persists across failed refreshes; the original Err is still returned to the frontend onError handler. - Include gemini_flash_lite tier in the tray summary with label "l". Matches the frontend SubscriptionQuotaFooter and keeps the worst emoji correct when lite is the highest utilization. - Add TIER_GEMINI_PRO / _FLASH / _FLASH_LITE constants in services/subscription.rs and reuse them in classify_gemini_model and sort_order. - Extract Provider::has_usage_script_enabled() to remove the duplicated meta.usage_script chain at two call sites. - Use db.get_provider_by_id in refresh_all_usage_in_tray instead of materialising the full provider map, and parallelise subscription and script futures via futures::future::join. - Narrow refresh_all_usage_in_tray to each section's effective current provider (script if enabled, else subscription when the provider is official). Hover refreshes now issue at most TRAY_SECTIONS.len() outbound requests. - Add 10 unit tests in tray::tests covering Claude/Codex h/w dispatch, Gemini p/f/l dispatch (including lite-only and lite-worst cases), and success/failure guards.
先合了,回头再研究,哈哈 |
…n1231#2184) * feat: add Rust-side write-through usage cache Introduce an in-memory UsageCache on AppState that the existing usage query commands populate on success. The cache is read-only to the rest of the app today; the next commit consumes it from the tray menu. - New services::usage_cache module with split maps: subscription keyed by AppType, script keyed by (AppType, provider_id). - AppType gains Eq + Hash so it can be used as a HashMap key. - commands::subscription::get_subscription_quota now takes State<AppState> and writes through on success (signature change is invisible to the frontend — Tauri injects State automatically). - commands::provider::queryProviderUsage body extracted into an inner async fn; the public command wraps it with write-through, covering Copilot, coding-plan, balance, and generic script paths uniformly. Cache is in-memory only; auto-query interval and the upcoming tray refresh action rebuild it after restarts. * feat(tray): surface cached usage in the system tray menu Read UsageCache populated by the previous commit and render it in three places, scoped to whatever TRAY_SECTIONS covers (Claude/Codex/Gemini): 1. Inline suffix on each provider submenu item "AnyProvider · 🟢 5h 18% / 7d 23%" 2. Disabled summary row per visible app under "Show Main" "Claude · Anthropic Official · 🟢 5h 18% / 7d 23%" 3. "Refresh all usage" menu item that triggers get_subscription_quota + queryProviderUsage for every applicable provider, then rebuilds the tray menu via the existing refresh_tray_menu path. Color encoding uses emoji (🟢 <70% / 🟠 70-89% / 🔴 ≥90%) since Tauri 2 tray labels are plain text. Missing cache entry leaves the label unchanged — tray never issues network requests when opened. Three new i18n-ready strings live in TrayTexts (en/zh/ja), following the existing pattern for tray text. Closes farion1231#2178. * feat(usage): bridge tray UsageCache writes to frontend React Query Why: tray hover triggers backend-only refresh that wrote to UsageCache but never notified the frontend, leaving main UI stale while tray showed fresh numbers. Emit a payload-carrying event after each cache write so React Query can setQueryData directly, keeping both views in sync without duplicate fetches. * fix(tray): skip hidden apps on hover refresh and drop stale disabled-script cache Address P2 findings from automated review on farion1231#2184: 1. refresh_all_usage_in_tray now filters TRAY_SECTIONS by settings.visible_apps before scheduling subscription/script queries, matching create_tray_menu and preventing wasted external API calls (and rate-limit/auth-error log noise) for apps the user has hidden. 2. format_usage_suffix only trusts the script cache when provider.meta.usage_script is still enabled; when a script is disabled/removed the cached suffix is now invalidated so the tray label no longer shows stale data indefinitely. * refactor: consolidate codex provider helpers and fix test semantics - Add Provider::is_codex_oauth() and Provider::codex_fast_mode_enabled() to eliminate duplicated meta extraction in claude.rs and stream_check.rs - Fix non-codex-oauth tests to pass codex_fast_mode=false (was true, harmless but semantically misleading) - Remove redundant is_dir() guard after resolve_skill_source_dir already guarantees the returned path is a directory * style: apply cargo fmt * fix(tray): reflect failed refreshes in cache and support Gemini flash-lite Follow-up to the tray usage-display feature addressing review feedback: - Write snapshots for both Ok(success:false) and Err paths in queryProviderUsage / get_subscription_quota so stale success data no longer persists across failed refreshes; the original Err is still returned to the frontend onError handler. - Include gemini_flash_lite tier in the tray summary with label "l". Matches the frontend SubscriptionQuotaFooter and keeps the worst emoji correct when lite is the highest utilization. - Add TIER_GEMINI_PRO / _FLASH / _FLASH_LITE constants in services/subscription.rs and reuse them in classify_gemini_model and sort_order. - Extract Provider::has_usage_script_enabled() to remove the duplicated meta.usage_script chain at two call sites. - Use db.get_provider_by_id in refresh_all_usage_in_tray instead of materialising the full provider map, and parallelise subscription and script futures via futures::future::join. - Narrow refresh_all_usage_in_tray to each section's effective current provider (script if enabled, else subscription when the provider is official). Hover refreshes now issue at most TRAY_SECTIONS.len() outbound requests. - Add 10 unit tests in tray::tests covering Claude/Codex h/w dispatch, Gemini p/f/l dispatch (including lite-only and lite-worst cases), and success/failure guards. --------- Co-authored-by: Jason <[email protected]>
Summary / 概述
Surface the cached provider usage in the system tray menu so users can see how much quota each account has left without opening the main window.
What ships (scoped to
TRAY_SECTIONStoday — Claude / Codex / Gemini):Claude · Claude Official · 🟢 h9% w27%(5-hour window, 7-day / weekly window).utilizationColor.Under the hood:
services::usage_cache::UsageCacheonAppState— in-memory, write-through fromget_subscription_quotaandqueryProviderUsage. Rendering reads only the cache — no network requests when opening the menu.AtomicBoolflag + 50 ms window, so a burst of successful usage writes only rebuilds the menu once.refresh_all_usage_in_trayparallelises the per-section subscription calls and per-provider script calls withfutures::future::join_all.AppTypegetsEq/Hashderives to serve as a HashMap key.Design notes driven by issue discussion:
3h34m/4d11h) next to each percentage, but the label got too long to stay readable. Happy to land that as a follow-up commit if you have a preferred layout — we already cache theresets_attimestamps.Related Issue / 关联 Issue
Fixes #2178 #2153
Screenshots / 截图
Checklist / 检查清单