refactor: gate balance status item behind DeepSeek provider

This commit is contained in:
ts25504
2026-05-16 11:12:03 +08:00
committed by Hu Qiantao
parent e774c940aa
commit 6b70d27cc8
4 changed files with 61 additions and 20 deletions
+32 -10
View File
@@ -800,7 +800,6 @@ impl StatusItem {
StatusItem::ReasoningReplay,
StatusItem::Cache,
StatusItem::Tokens,
StatusItem::Balance,
]
}
@@ -821,11 +820,8 @@ impl StatusItem {
StatusItem::GitBranch => "git_branch",
StatusItem::LastToolElapsed => "last_tool_elapsed",
StatusItem::RateLimit => "rate_limit",
<<<<<<< HEAD
StatusItem::Tokens => "tokens",
=======
StatusItem::Balance => "balance",
>>>>>>> 4bc823e6 (feat: add account balance status bar item)
}
}
@@ -846,11 +842,8 @@ impl StatusItem {
StatusItem::GitBranch => "Git branch",
StatusItem::LastToolElapsed => "Last tool elapsed",
StatusItem::RateLimit => "Rate-limit remaining",
<<<<<<< HEAD
StatusItem::Tokens => "Session tokens",
=======
StatusItem::Balance => "Account balance",
>>>>>>> 4bc823e6 (feat: add account balance status bar item)
}
}
@@ -872,11 +865,8 @@ impl StatusItem {
StatusItem::GitBranch => "current workspace branch",
StatusItem::LastToolElapsed => "ms of the most recent tool call (placeholder)",
StatusItem::RateLimit => "remaining requests in the budget (placeholder)",
<<<<<<< HEAD
StatusItem::Tokens => "input / cache-hit / output token totals",
=======
StatusItem::Balance => "topped-up + granted balance from DeepSeek",
>>>>>>> 4bc823e6 (feat: add account balance status bar item)
}
}
@@ -914,6 +904,19 @@ impl StatusItem {
| StatusItem::Balance
)
}
/// Whether this item is relevant for `provider`. Provider-specific
/// items return `false` for unsupported providers so the picker doesn't
/// offer toggles that can never show useful data.
#[must_use]
pub fn is_available_for(self, provider: ApiProvider) -> bool {
match self {
StatusItem::Balance => {
matches!(provider, ApiProvider::Deepseek | ApiProvider::DeepseekCN)
}
_ => true,
}
}
}
/// Resolved retry policy with defaults applied.
@@ -7555,4 +7558,23 @@ model = "deepseek-ai/deepseek-v4-pro"
let deserialized: ProviderCapability = serde_json::from_value(json).unwrap();
assert_eq!(cap, deserialized);
}
#[test]
fn status_item_balance_available_only_for_deepseek_providers() {
// Balance item should only be offered for DeepSeek / DeepSeekCN.
assert!(StatusItem::Balance.is_available_for(ApiProvider::Deepseek));
assert!(StatusItem::Balance.is_available_for(ApiProvider::DeepseekCN));
// Sanity: all other known providers should hide the Balance toggle.
assert!(!StatusItem::Balance.is_available_for(ApiProvider::Openrouter));
assert!(!StatusItem::Balance.is_available_for(ApiProvider::Novita));
assert!(!StatusItem::Balance.is_available_for(ApiProvider::NvidiaNim));
assert!(!StatusItem::Balance.is_available_for(ApiProvider::Fireworks));
assert!(!StatusItem::Balance.is_available_for(ApiProvider::Sglang));
assert!(!StatusItem::Balance.is_available_for(ApiProvider::Vllm));
assert!(!StatusItem::Balance.is_available_for(ApiProvider::Ollama));
assert!(!StatusItem::Balance.is_available_for(ApiProvider::Openai));
assert!(!StatusItem::Balance.is_available_for(ApiProvider::Atlascloud));
// Other StatusItem variants should be available everywhere.
assert!(StatusItem::Mode.is_available_for(ApiProvider::Ollama));
}
}
+1
View File
@@ -4908,6 +4908,7 @@ async fn apply_command_result(
app.view_stack
.push(crate::tui::views::status_picker::StatusPickerView::new(
&app.status_items,
app.api_provider,
));
}
}
+5 -1
View File
@@ -5683,13 +5683,17 @@ fn render_footer_from_with_default_items_renders_mode_and_model() {
}
#[test]
fn default_footer_keeps_prefix_stability_opt_in() {
fn default_footer_excludes_provider_specific_diagnostic_chips() {
let items = crate::config::StatusItem::default_footer();
assert!(
!items.contains(&crate::config::StatusItem::PrefixStability),
"prefix stability is a diagnostic chip and should not crowd the default footer"
);
assert!(
!items.contains(&crate::config::StatusItem::Balance),
"balance is DeepSeek-only and should not crowd the default footer for non-DeepSeek users"
);
assert!(
items.contains(&crate::config::StatusItem::Cache),
"default footer should still include provider-reported cache hit rate"
+23 -9
View File
@@ -20,7 +20,7 @@ use ratatui::{
widgets::{Block, Borders, Clear, Padding, Paragraph, Widget},
};
use crate::config::StatusItem;
use crate::config::{ApiProvider, StatusItem};
use crate::palette;
use crate::tui::views::{ModalKind, ModalView, ViewAction, ViewEvent};
@@ -47,8 +47,12 @@ pub struct StatusPickerView {
impl StatusPickerView {
#[must_use]
pub fn new(active: &[StatusItem]) -> Self {
let rows: Vec<StatusItem> = StatusItem::all().to_vec();
pub fn new(active: &[StatusItem], provider: ApiProvider) -> Self {
let rows: Vec<StatusItem> = StatusItem::all()
.iter()
.filter(|item| item.is_available_for(provider))
.copied()
.collect();
let selected: Vec<bool> = rows.iter().map(|item| active.contains(item)).collect();
Self {
rows,
@@ -297,14 +301,14 @@ mod tests {
#[test]
fn opens_with_active_items_pre_selected() {
let active = StatusItem::default_footer();
let view = StatusPickerView::new(&active);
let view = StatusPickerView::new(&active, ApiProvider::Deepseek);
assert_eq!(view.current_selection(), active);
}
#[test]
fn space_toggles_current_row_and_emits_live_preview() {
let active = StatusItem::default_footer();
let mut view = StatusPickerView::new(&active);
let mut view = StatusPickerView::new(&active, ApiProvider::Deepseek);
// Cursor starts at row 0 = StatusItem::Mode (currently checked).
let action = view.handle_key(KeyEvent::new(KeyCode::Char(' '), KeyModifiers::NONE));
match action {
@@ -319,7 +323,7 @@ mod tests {
#[test]
fn enter_emits_final_save() {
let active = StatusItem::default_footer();
let mut view = StatusPickerView::new(&active);
let mut view = StatusPickerView::new(&active, ApiProvider::Deepseek);
let action = view.handle_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
match action {
ViewAction::EmitAndClose(ViewEvent::StatusItemsUpdated { final_save, .. }) => {
@@ -332,7 +336,7 @@ mod tests {
#[test]
fn esc_reverts_to_snapshot() {
let active = StatusItem::default_footer();
let mut view = StatusPickerView::new(&active);
let mut view = StatusPickerView::new(&active, ApiProvider::Deepseek);
// Toggle a few items off so the working set diverges from snapshot.
view.handle_key(KeyEvent::new(KeyCode::Char(' '), KeyModifiers::NONE));
view.move_down();
@@ -350,7 +354,7 @@ mod tests {
#[test]
fn select_all_and_select_none_keys_work() {
let active: Vec<StatusItem> = Vec::new();
let mut view = StatusPickerView::new(&active);
let mut view = StatusPickerView::new(&active, ApiProvider::Deepseek);
let action = view.handle_key(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE));
match action {
ViewAction::Emit(ViewEvent::StatusItemsUpdated { items, .. }) => {
@@ -370,7 +374,7 @@ mod tests {
#[test]
fn arrow_keys_move_cursor_within_bounds() {
let active = StatusItem::default_footer();
let mut view = StatusPickerView::new(&active);
let mut view = StatusPickerView::new(&active, ApiProvider::Deepseek);
assert_eq!(view.cursor, 0);
view.handle_key(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE));
assert_eq!(view.cursor, 1);
@@ -382,4 +386,14 @@ mod tests {
}
assert_eq!(view.cursor, StatusItem::all().len() - 1);
}
#[test]
fn balance_excluded_for_non_deepseek_provider() {
let active = StatusItem::default_footer();
let view = StatusPickerView::new(&active, ApiProvider::Openrouter);
// Balance should not appear as a row for non-DeepSeek providers.
assert!(!view.rows.contains(&StatusItem::Balance));
// Mode should still be present.
assert!(view.rows.contains(&StatusItem::Mode));
}
}