feat(i18n): add FanoutCounts MessageId, wire into FanoutCard stats line
This commit is contained in:
@@ -503,6 +503,8 @@ pub enum MessageId {
|
||||
CtxMenuContextInspectorDesc,
|
||||
CtxMenuHelp,
|
||||
CtxMenuHelpDesc,
|
||||
// Agent fanout card.
|
||||
FanoutCounts,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -782,6 +784,7 @@ pub const ALL_MESSAGE_IDS: &[MessageId] = &[
|
||||
MessageId::CtxMenuContextInspectorDesc,
|
||||
MessageId::CtxMenuHelp,
|
||||
MessageId::CtxMenuHelpDesc,
|
||||
MessageId::FanoutCounts,
|
||||
];
|
||||
|
||||
pub fn tr(locale: Locale, id: MessageId) -> &'static str {
|
||||
@@ -1370,6 +1373,9 @@ fn english(id: MessageId) -> &'static str {
|
||||
MessageId::CtxMenuContextInspectorDesc => "active context and cache hints",
|
||||
MessageId::CtxMenuHelp => "Help",
|
||||
MessageId::CtxMenuHelpDesc => "keybindings and commands",
|
||||
MessageId::FanoutCounts => {
|
||||
"{done} done · {running} running · {failed} failed · {pending} pending"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1826,6 +1832,9 @@ fn vietnamese(id: MessageId) -> Option<&'static str> {
|
||||
MessageId::CtxMenuContextInspectorDesc => "ngữ cảnh đang hoạt động và gợi ý bộ nhớ đệm",
|
||||
MessageId::CtxMenuHelp => "Trợ giúp",
|
||||
MessageId::CtxMenuHelpDesc => "phím tắt và lệnh",
|
||||
MessageId::FanoutCounts => {
|
||||
"{done} hoàn thành · {running} đang chạy · {failed} thất bại · {pending} chờ"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1839,6 +1848,9 @@ fn traditional_chinese(id: MessageId) -> Option<&'static str> {
|
||||
MessageId::TranslationComplete => "翻譯完成",
|
||||
MessageId::TranslationFailed => "翻譯失敗",
|
||||
MessageId::FooterBalancePrefix => "餘額",
|
||||
MessageId::FanoutCounts => {
|
||||
"{done} 已完成 · {running} 運行中 · {failed} 失敗 · {pending} 等待中"
|
||||
}
|
||||
other => chinese_simplified(other)?,
|
||||
})
|
||||
}
|
||||
@@ -2256,6 +2268,9 @@ fn japanese(id: MessageId) -> Option<&'static str> {
|
||||
MessageId::CtxMenuContextInspectorDesc => "アクティブなコンテキストとキャッシュヒント",
|
||||
MessageId::CtxMenuHelp => "ヘルプ",
|
||||
MessageId::CtxMenuHelpDesc => "キー操作とコマンド",
|
||||
MessageId::FanoutCounts => {
|
||||
"{done} 完了 · {running} 実行中 · {failed} 失敗 · {pending} 待機"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2612,6 +2627,9 @@ fn chinese_simplified(id: MessageId) -> Option<&'static str> {
|
||||
MessageId::CtxMenuContextInspectorDesc => "活动上下文和缓存提示",
|
||||
MessageId::CtxMenuHelp => "帮助",
|
||||
MessageId::CtxMenuHelpDesc => "快捷键和命令",
|
||||
MessageId::FanoutCounts => {
|
||||
"{done} 已完成 · {running} 运行中 · {failed} 失败 · {pending} 等待中"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3052,6 +3070,9 @@ fn portuguese_brazil(id: MessageId) -> Option<&'static str> {
|
||||
MessageId::CtxMenuContextInspectorDesc => "contexto ativo e dicas de cache",
|
||||
MessageId::CtxMenuHelp => "Ajuda",
|
||||
MessageId::CtxMenuHelpDesc => "atalhos de teclado e comandos",
|
||||
MessageId::FanoutCounts => {
|
||||
"{done} concluído · {running} executando · {failed} falhou · {pending} pendente"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3502,6 +3523,9 @@ fn spanish_latin_america(id: MessageId) -> Option<&'static str> {
|
||||
MessageId::CtxMenuContextInspectorDesc => "contexto activo y sugerencias de caché",
|
||||
MessageId::CtxMenuHelp => "Ayuda",
|
||||
MessageId::CtxMenuHelpDesc => "atajos de teclado y comandos",
|
||||
MessageId::FanoutCounts => {
|
||||
"{done} completado · {running} ejecutando · {failed} falló · {pending} pendiente"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -154,7 +154,10 @@ pub(super) fn handle_subagent_mailbox(app: &mut App, seq: u64, message: &Mailbox
|
||||
card.claim_pending_worker(&agent_id, AgentLifecycle::Running);
|
||||
app.subagent_card_index.insert(agent_id, idx);
|
||||
} else {
|
||||
let mut card = FanoutCard::new(dispatch_kind.unwrap_or("rlm_eval").to_string());
|
||||
let mut card = FanoutCard::new(
|
||||
dispatch_kind.unwrap_or("rlm_eval").to_string(),
|
||||
app.ui_locale,
|
||||
);
|
||||
card.upsert_worker(&agent_id, AgentLifecycle::Running);
|
||||
app.add_message(HistoryCell::SubAgent(SubAgentCell::Fanout(card)));
|
||||
let idx = app.history.len().saturating_sub(1);
|
||||
|
||||
@@ -2252,7 +2252,7 @@ mod tests {
|
||||
#[test]
|
||||
fn subagent_view_agents_includes_live_fanout_workers_when_cache_is_empty() {
|
||||
let mut app = create_test_app();
|
||||
let mut card = FanoutCard::new("rlm").with_workers(["chunk_1", "chunk_2"]);
|
||||
let mut card = FanoutCard::new("rlm", app.ui_locale).with_workers(["chunk_1", "chunk_2"]);
|
||||
card.upsert_worker("chunk_1", AgentLifecycle::Completed);
|
||||
card.upsert_worker("chunk_2", AgentLifecycle::Running);
|
||||
app.add_message(HistoryCell::SubAgent(SubAgentCell::Fanout(card)));
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::text::{Line, Span};
|
||||
|
||||
use crate::localization::{Locale, MessageId, tr};
|
||||
use crate::palette;
|
||||
use crate::tools::subagent::MailboxMessage;
|
||||
use crate::tui::widgets::tool_card::{ToolFamily, family_glyph, family_label};
|
||||
@@ -193,14 +194,16 @@ impl WorkerSlot {
|
||||
pub struct FanoutCard {
|
||||
pub kind: String,
|
||||
pub workers: Vec<WorkerSlot>,
|
||||
pub locale: Locale,
|
||||
}
|
||||
|
||||
impl FanoutCard {
|
||||
#[must_use]
|
||||
pub fn new(kind: impl Into<String>) -> Self {
|
||||
pub fn new(kind: impl Into<String>, locale: Locale) -> Self {
|
||||
Self {
|
||||
kind: kind.into(),
|
||||
workers: Vec::new(),
|
||||
locale,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,9 +312,11 @@ impl FanoutCard {
|
||||
lines.push(Line::from(vec![
|
||||
Span::styled(" ", Style::default()),
|
||||
Span::styled(
|
||||
format!(
|
||||
"{done} done \u{00B7} {running} running \u{00B7} {failed} failed \u{00B7} {pending} pending"
|
||||
),
|
||||
tr(self.locale, MessageId::FanoutCounts)
|
||||
.replace("{done}", &done.to_string())
|
||||
.replace("{running}", &running.to_string())
|
||||
.replace("{failed}", &failed.to_string())
|
||||
.replace("{pending}", &pending.to_string()),
|
||||
Style::default().fg(palette::TEXT_MUTED),
|
||||
),
|
||||
]));
|
||||
@@ -632,7 +637,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn fanout_card_dot_grid_renders_stateful_worker_slots() {
|
||||
let mut card = FanoutCard::new("fanout")
|
||||
let mut card = FanoutCard::new("fanout", Locale::En)
|
||||
.with_workers(["w_1", "w_2", "w_3", "w_4", "w_5", "w_6", "w_7"]);
|
||||
card.upsert_worker("w_1", AgentLifecycle::Completed);
|
||||
card.upsert_worker("w_2", AgentLifecycle::Completed);
|
||||
@@ -649,7 +654,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn fanout_card_aggregate_counts_match_dot_grid() {
|
||||
let mut card = FanoutCard::new("rlm").with_workers(["w_1", "w_2", "w_3", "w_4"]);
|
||||
let mut card =
|
||||
FanoutCard::new("rlm", Locale::En).with_workers(["w_1", "w_2", "w_3", "w_4"]);
|
||||
card.upsert_worker("w_1", AgentLifecycle::Completed);
|
||||
card.upsert_worker("w_2", AgentLifecycle::Completed);
|
||||
card.upsert_worker("w_3", AgentLifecycle::Completed);
|
||||
@@ -672,7 +678,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn fanout_apply_inserts_unknown_worker_via_child_spawned() {
|
||||
let mut card = FanoutCard::new("fanout");
|
||||
let mut card = FanoutCard::new("fanout", Locale::En);
|
||||
let msg = MailboxMessage::ChildSpawned {
|
||||
parent_id: "root".into(),
|
||||
child_id: "agent_late".into(),
|
||||
@@ -685,7 +691,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn fanout_started_claims_seeded_pending_slot_without_growing_grid() {
|
||||
let mut card = FanoutCard::new("fanout").with_workers(["task:a", "task:b"]);
|
||||
let mut card = FanoutCard::new("fanout", Locale::En).with_workers(["task:a", "task:b"]);
|
||||
let started =
|
||||
MailboxMessage::started("agent_live", crate::tools::subagent::SubAgentType::General);
|
||||
|
||||
@@ -700,7 +706,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn fanout_apply_transitions_worker_through_lifecycle() {
|
||||
let mut card = FanoutCard::new("fanout").with_workers(["w_1"]);
|
||||
let mut card = FanoutCard::new("fanout", Locale::En).with_workers(["w_1"]);
|
||||
let started = MailboxMessage::started("w_1", crate::tools::subagent::SubAgentType::General);
|
||||
apply_to_fanout(&mut card, &started);
|
||||
assert_eq!(card.workers[0].status, AgentLifecycle::Running);
|
||||
@@ -729,7 +735,7 @@ mod tests {
|
||||
];
|
||||
for (total, done, expected) in cases {
|
||||
let ids: Vec<String> = (0..*total).map(|i| format!("w_{i}")).collect();
|
||||
let mut card = FanoutCard::new("fanout").with_workers(ids.iter().cloned());
|
||||
let mut card = FanoutCard::new("fanout", Locale::En).with_workers(ids.iter().cloned());
|
||||
for id in ids.iter().take(*done) {
|
||||
card.upsert_worker(id, AgentLifecycle::Completed);
|
||||
}
|
||||
@@ -740,4 +746,25 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fanout_counts_are_localized() {
|
||||
let ids: Vec<String> = (0..16).map(|i| format!("w_{i}")).collect();
|
||||
let mut card = FanoutCard::new("fanout", Locale::ZhHans).with_workers(ids.iter().cloned());
|
||||
for id in ids.iter().take(12) {
|
||||
card.upsert_worker(id, AgentLifecycle::Completed);
|
||||
}
|
||||
card.upsert_worker("w_12", AgentLifecycle::Running);
|
||||
// w_13..w_15 stay Pending; 0 failed
|
||||
|
||||
let rendered = render_to_strings(&card.render_lines(80));
|
||||
let stats = rendered
|
||||
.iter()
|
||||
.find(|line| line.contains('·'))
|
||||
.expect("counts line present");
|
||||
assert!(stats.contains("已完成"), "{stats}");
|
||||
assert!(stats.contains("运行中"), "{stats}");
|
||||
assert!(stats.contains("失败"), "{stats}");
|
||||
assert!(stats.contains("等待中"), "{stats}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user