diff --git a/chrs-observer/src/main.rs b/chrs-observer/src/main.rs index fedb5738..76ecda6a 100644 --- a/chrs-observer/src/main.rs +++ b/chrs-observer/src/main.rs @@ -2,8 +2,8 @@ use ratatui::{ backend::CrosstermBackend, - widgets::{Block, Borders, Paragraph, List, ListItem, Gauge}, - layout::{Layout, Constraint, Direction}, + widgets::{Block, Borders, Paragraph, List, ListItem, Gauge, BorderType}, + layout::{Layout, Constraint, Direction, Alignment}, style::{Color, Modifier, Style}, Terminal, }; @@ -38,8 +38,9 @@ impl App { } fn add_log(&mut self, log: String) { - self.logs.push(log); - if self.logs.len() > 50 { + let now = chrono::Local::now().format("%H:%M:%S").to_string(); + self.logs.push(format!("[{}] {}", now, log)); + if self.logs.len() > 100 { self.logs.remove(0); } } @@ -73,7 +74,7 @@ async fn main() -> Result<(), Box> { if msg.topic == "chorus-heartbeat" { if let Ok(peer) = serde_json::from_slice::(&msg.payload) { 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); } @@ -115,25 +116,55 @@ async fn main() -> Result<(), Box> { } fn ui(f: &mut ratatui::Frame, app: &App) { + let size = f.size(); let chunks = Layout::default() .direction(Direction::Vertical) .constraints( [ - Constraint::Length(3), // Pulse Gauge - Constraint::Min(0), // Body + Constraint::Length(3), // Pulse and Status + Constraint::Min(0), // Main Body ] .as_ref(), ) - .split(f.size()); + .split(size); // --- 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 pulse_title = format!(" CHORUS CLUSTER PULSE | {} BPM | Beat: {}/8 ", app.pulse_bpm, app.beat_index); let pulse_gauge = Gauge::default() - .block(Block::default().borders(Borders::ALL).title(pulse_title)) - .gauge_style(Style::default().fg(Color::Cyan).bg(Color::Black).add_modifier(Modifier::BOLD)) - .percent(beat_progress as u16); - f.render_widget(pulse_gauge, chunks[0]); + .block(Block::default().borders(Borders::ALL).border_type(BorderType::Rounded).title(" Cluster Pulse ")) + .gauge_style(Style::default().fg(Color::Cyan).bg(Color::DarkGray)) + .percent(beat_progress as u16) + .label(beat_viz); + f.render_widget(pulse_gauge, header_chunks[1]); // --- Body (Peers and Logs) --- let body_chunks = Layout::default() @@ -150,12 +181,14 @@ fn ui(f: &mut ratatui::Frame, app: &App) { // Peer List let peers: Vec = app.peers.values() .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)) }) .collect(); 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]); // Logs