// thalamus_screen.rs — F5: presence, idle state, and channel status use ratatui::{ layout::Rect, style::{Color, Style}, text::{Line, Span}, widgets::{Block, Borders, Paragraph, Wrap}, Frame, crossterm::event::KeyCode, }; use super::{App, ScreenView, screen_legend}; pub(crate) struct ThalamusScreen { sampling_selected: usize, scroll: u16, } impl ThalamusScreen { pub fn new() -> Self { Self { sampling_selected: 0, scroll: 0 } } } impl ScreenView for ThalamusScreen { fn label(&self) -> &'static str { "thalamus" } fn tick(&mut self, frame: &mut Frame, area: Rect, events: &[ratatui::crossterm::event::Event], app: &mut App) { // Handle keys for event in events { if let ratatui::crossterm::event::Event::Key(key) = event { if key.kind != ratatui::crossterm::event::KeyEventKind::Press { continue; } match key.code { KeyCode::Up => { self.sampling_selected = self.sampling_selected.saturating_sub(1); } KeyCode::Down => { self.sampling_selected = (self.sampling_selected + 1).min(2); } KeyCode::Right => { let _delta = match self.sampling_selected { 0 => 0.05, 1 => 0.05, 2 => 5.0, _ => 0.0, }; } KeyCode::Left => { let _delta = match self.sampling_selected { 0 => -0.05, 1 => -0.05, 2 => -5.0, _ => 0.0, }; } _ => {} } } } // Draw let section = Style::default().fg(Color::Yellow); let dim = Style::default().fg(Color::DarkGray); let mut lines: Vec = Vec::new(); // Presence / idle state lines.push(Line::styled("── Presence ──", section)); lines.push(Line::raw("")); if let Some(ref idle) = app.idle_info { let presence = if idle.user_present { Span::styled("present", Style::default().fg(Color::Green)) } else { Span::styled("away", Style::default().fg(Color::DarkGray)) }; lines.push(Line::from(vec![ Span::raw(" User: "), presence, Span::raw(format!(" (last {:.0}s ago)", idle.since_activity)), ])); lines.push(Line::raw(format!(" Activity: {:.1}%", idle.activity_ewma * 100.0))); lines.push(Line::raw(format!(" Idle state: {}", idle.block_reason))); if idle.dreaming { lines.push(Line::styled(" ◆ dreaming", Style::default().fg(Color::Magenta))); } if idle.sleeping { lines.push(Line::styled(" ◆ sleeping", Style::default().fg(Color::Blue))); } } else { lines.push(Line::styled(" not initialized", dim)); } lines.push(Line::raw("")); // Sampling parameters lines.push(Line::styled("── Sampling (←/→ adjust) ──", section)); lines.push(Line::raw("")); let params = [ format!("temperature: {:.2}", app.temperature), format!("top_p: {:.2}", app.top_p), format!("top_k: {}", app.top_k), ]; for (i, label) in params.iter().enumerate() { let prefix = if i == self.sampling_selected { "▸ " } else { " " }; let style = if i == self.sampling_selected { Style::default().fg(Color::Cyan) } else { Style::default() }; lines.push(Line::styled(format!("{}{}", prefix, label), style)); } lines.push(Line::raw("")); // Channel status lines.push(Line::styled("── Channels ──", section)); lines.push(Line::raw("")); if app.channel_status.is_empty() { lines.push(Line::styled(" no channels configured", dim)); } else { for ch in &app.channel_status { let (symbol, color) = if ch.connected { ("●", Color::Green) } else { ("○", Color::Red) }; let unread_str = if ch.unread > 0 { format!(" ({} unread)", ch.unread) } else { String::new() }; lines.push(Line::from(vec![ Span::raw(" "), Span::styled(symbol, Style::default().fg(color)), Span::raw(format!(" {:<24}", ch.name)), Span::styled(if ch.connected { "connected" } else { "disconnected" }, Style::default().fg(color)), Span::styled(unread_str, Style::default().fg(Color::Yellow)), ])); } } let block = Block::default() .title_top(Line::from(screen_legend()).left_aligned()) .title_top(Line::from(" thalamus ").right_aligned()) .borders(Borders::ALL) .border_style(Style::default().fg(Color::Cyan)); let para = Paragraph::new(lines) .block(block) .wrap(Wrap { trim: false }) .scroll((self.scroll, 0)); frame.render_widget(para, area); } }