UI: Refine rhythmic display in chrs-observer TUI
This commit is contained in:
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
backend::CrosstermBackend,
|
backend::CrosstermBackend,
|
||||||
widgets::{Block, Borders, Paragraph, List, ListItem, Gauge},
|
widgets::{Block, Borders, Paragraph, List, ListItem, Gauge, BorderType},
|
||||||
layout::{Layout, Constraint, Direction},
|
layout::{Layout, Constraint, Direction, Alignment},
|
||||||
style::{Color, Modifier, Style},
|
style::{Color, Modifier, Style},
|
||||||
Terminal,
|
Terminal,
|
||||||
};
|
};
|
||||||
@@ -38,8 +38,9 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_log(&mut self, log: String) {
|
fn add_log(&mut self, log: String) {
|
||||||
self.logs.push(log);
|
let now = chrono::Local::now().format("%H:%M:%S").to_string();
|
||||||
if self.logs.len() > 50 {
|
self.logs.push(format!("[{}] {}", now, log));
|
||||||
|
if self.logs.len() > 100 {
|
||||||
self.logs.remove(0);
|
self.logs.remove(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +74,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
if msg.topic == "chorus-heartbeat" {
|
if msg.topic == "chorus-heartbeat" {
|
||||||
if let Ok(peer) = serde_json::from_slice::<Peer>(&msg.payload) {
|
if let Ok(peer) = serde_json::from_slice::<Peer>(&msg.payload) {
|
||||||
if !app.peers.contains_key(&peer.id) {
|
if !app.peers.contains_key(&peer.id) {
|
||||||
app.add_log(format!("[P2P] Discovered Agent: {} ({:?})", peer.id, peer.role));
|
app.add_log(format!("Discovered Agent: {} ({:?})", peer.id, peer.role));
|
||||||
}
|
}
|
||||||
app.peers.insert(peer.id.clone(), peer);
|
app.peers.insert(peer.id.clone(), peer);
|
||||||
}
|
}
|
||||||
@@ -115,25 +116,55 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn ui(f: &mut ratatui::Frame, app: &App) {
|
fn ui(f: &mut ratatui::Frame, app: &App) {
|
||||||
|
let size = f.size();
|
||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(
|
.constraints(
|
||||||
[
|
[
|
||||||
Constraint::Length(3), // Pulse Gauge
|
Constraint::Length(3), // Pulse and Status
|
||||||
Constraint::Min(0), // Body
|
Constraint::Min(0), // Main Body
|
||||||
]
|
]
|
||||||
.as_ref(),
|
.as_ref(),
|
||||||
)
|
)
|
||||||
.split(f.size());
|
.split(size);
|
||||||
|
|
||||||
// --- Header / Pulse ---
|
// --- Header / Pulse ---
|
||||||
|
let header_chunks = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Percentage(40), // BPM & Status
|
||||||
|
Constraint::Percentage(60), // Rhythm Progress
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(chunks[0]);
|
||||||
|
|
||||||
|
let status_text = format!(" CHORUS Cluster | {} BPM | Agents: {}", app.pulse_bpm, app.peers.len());
|
||||||
|
let status_para = Paragraph::new(status_text)
|
||||||
|
.block(Block::default().borders(Borders::ALL).border_type(BorderType::Rounded))
|
||||||
|
.style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD))
|
||||||
|
.alignment(Alignment::Left);
|
||||||
|
f.render_widget(status_para, header_chunks[0]);
|
||||||
|
|
||||||
|
// Visual Beat Counter: [ 1 2 3 4 5 6 7 8 ]
|
||||||
|
let mut beat_viz = String::from(" [");
|
||||||
|
for i in 1..=8 {
|
||||||
|
if i == app.beat_index {
|
||||||
|
beat_viz.push_str(&format!(" {} ", i));
|
||||||
|
} else {
|
||||||
|
beat_viz.push_str(" . ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
beat_viz.push(']');
|
||||||
|
|
||||||
let beat_progress = (app.beat_index as f32 / 8.0) * 100.0;
|
let beat_progress = (app.beat_index as f32 / 8.0) * 100.0;
|
||||||
let pulse_title = format!(" CHORUS CLUSTER PULSE | {} BPM | Beat: {}/8 ", app.pulse_bpm, app.beat_index);
|
|
||||||
let pulse_gauge = Gauge::default()
|
let pulse_gauge = Gauge::default()
|
||||||
.block(Block::default().borders(Borders::ALL).title(pulse_title))
|
.block(Block::default().borders(Borders::ALL).border_type(BorderType::Rounded).title(" Cluster Pulse "))
|
||||||
.gauge_style(Style::default().fg(Color::Cyan).bg(Color::Black).add_modifier(Modifier::BOLD))
|
.gauge_style(Style::default().fg(Color::Cyan).bg(Color::DarkGray))
|
||||||
.percent(beat_progress as u16);
|
.percent(beat_progress as u16)
|
||||||
f.render_widget(pulse_gauge, chunks[0]);
|
.label(beat_viz);
|
||||||
|
f.render_widget(pulse_gauge, header_chunks[1]);
|
||||||
|
|
||||||
// --- Body (Peers and Logs) ---
|
// --- Body (Peers and Logs) ---
|
||||||
let body_chunks = Layout::default()
|
let body_chunks = Layout::default()
|
||||||
@@ -150,12 +181,14 @@ fn ui(f: &mut ratatui::Frame, app: &App) {
|
|||||||
// Peer List
|
// Peer List
|
||||||
let peers: Vec<ListItem> = app.peers.values()
|
let peers: Vec<ListItem> = app.peers.values()
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
let content = format!(" {} [{:?}] Weight: {:.2}", p.id, p.role, p.resource_score);
|
let content = format!(" {} [{:?}] W: {:.2}", p.id, p.role, p.resource_score);
|
||||||
ListItem::new(content).style(Style::default().fg(Color::Yellow))
|
ListItem::new(content).style(Style::default().fg(Color::Yellow))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let peer_list = List::new(peers)
|
let peer_list = List::new(peers)
|
||||||
.block(Block::default().borders(Borders::ALL).title(" Active Agents "));
|
.block(Block::default().borders(Borders::ALL).title(" Active Agents "))
|
||||||
|
.highlight_style(Style::default().add_modifier(Modifier::BOLD))
|
||||||
|
.highlight_symbol(">> ");
|
||||||
f.render_widget(peer_list, body_chunks[0]);
|
f.render_widget(peer_list, body_chunks[0]);
|
||||||
|
|
||||||
// Logs
|
// Logs
|
||||||
|
|||||||
Reference in New Issue
Block a user