// subconscious_screen.rs — F3 subconscious agent overlay use ratatui::{ layout::Rect, style::{Color, Modifier, Style}, text::{Line, Span}, widgets::{Block, Borders, Paragraph, Wrap}, Frame, crossterm::event::{KeyCode, KeyEvent}, }; use super::{App, ScreenAction, ScreenView, screen_legend}; pub(crate) struct SubconsciousScreen { selected: usize, log_view: bool, scroll: u16, } impl SubconsciousScreen { pub fn new() -> Self { Self { selected: 0, log_view: false, scroll: 0 } } } impl ScreenView for SubconsciousScreen { fn label(&self) -> &'static str { "subconscious" } fn tick(&mut self, frame: &mut Frame, area: Rect, key: Option, app: &App) -> Option { // Handle keys if let Some(key) = key { match key.code { KeyCode::Up if !self.log_view => { self.selected = self.selected.saturating_sub(1); } KeyCode::Down if !self.log_view => { self.selected = (self.selected + 1).min(app.agent_state.len().saturating_sub(1)); } KeyCode::Enter | KeyCode::Right if !self.log_view => { self.log_view = true; self.scroll = 0; } KeyCode::Esc | KeyCode::Left if self.log_view => { self.log_view = false; } KeyCode::Esc if !self.log_view => return Some(ScreenAction::Switch(0)), KeyCode::PageUp => { self.scroll = self.scroll.saturating_sub(20); } KeyCode::PageDown => { self.scroll += 20; } _ => {} } } // Draw if self.log_view { self.draw_log(frame, area, app); } else { self.draw_list(frame, area, app); } None } } impl SubconsciousScreen { fn draw_list(&self, frame: &mut Frame, area: Rect, app: &App) { let mut lines: Vec = Vec::new(); let section = Style::default().fg(Color::Yellow); let hint = Style::default().fg(Color::DarkGray).add_modifier(Modifier::ITALIC); lines.push(Line::raw("")); lines.push(Line::styled("── Subconscious Agents ──", section)); lines.push(Line::styled(" (↑/↓ select, Enter/→ view log, Esc back)", hint)); lines.push(Line::raw("")); for (i, agent) in app.agent_state.iter().enumerate() { let selected = i == self.selected; let prefix = if selected { "▸ " } else { " " }; let bg = if selected { Style::default().bg(Color::DarkGray) } else { Style::default() }; let status = match (&agent.pid, &agent.phase) { (Some(pid), Some(phase)) => vec![ Span::styled(format!("{}{:<20}", prefix, agent.name), bg.fg(Color::Green)), Span::styled("● ", bg.fg(Color::Green)), Span::styled(format!("pid {} {}", pid, phase), bg), ], (None, Some(phase)) => vec![ Span::styled(format!("{}{:<20}", prefix, agent.name), bg.fg(Color::Cyan)), Span::styled("◆ ", bg.fg(Color::Cyan)), Span::styled(phase.clone(), bg), ], _ => vec![ Span::styled(format!("{}{:<20}", prefix, agent.name), bg.fg(Color::Gray)), Span::styled("○ idle", bg.fg(Color::DarkGray)), ], }; lines.push(Line::from(status)); } let block = Block::default() .title_top(Line::from(screen_legend()).left_aligned()) .title_top(Line::from(" subconscious ").right_aligned()) .borders(Borders::ALL) .border_style(Style::default().fg(Color::Cyan)); let para = Paragraph::new(lines).block(block).scroll((self.scroll, 0)); frame.render_widget(para, area); } fn draw_log(&self, frame: &mut Frame, area: Rect, app: &App) { let agent = app.agent_state.get(self.selected); let name = agent.map(|a| a.name.as_str()).unwrap_or("?"); let mut lines: Vec = Vec::new(); let section = Style::default().fg(Color::Yellow); let hint = Style::default().fg(Color::DarkGray).add_modifier(Modifier::ITALIC); lines.push(Line::raw("")); lines.push(Line::styled(format!("── {} ──", name), section)); lines.push(Line::styled(" (Esc/← back, PgUp/PgDn scroll)", hint)); lines.push(Line::raw("")); match agent.and_then(|a| a.pid) { Some(pid) => { let phase = agent.and_then(|a| a.phase.as_deref()).unwrap_or("?"); lines.push(Line::from(vec![ Span::styled(" Status: ", Style::default()), Span::styled(format!("● running pid {} phase: {}", pid, phase), Style::default().fg(Color::Green)), ])); } None => { lines.push(Line::styled(" Status: idle", Style::default().fg(Color::DarkGray))); } } if let Some(log_path) = agent.and_then(|a| a.log_path.as_ref()) { lines.push(Line::raw(format!(" Log: {}", log_path.display()))); } lines.push(Line::raw("")); lines.push(Line::styled("── Agent Log ──", section)); if let Some(content) = agent .and_then(|a| a.log_path.as_ref()) .and_then(|p| std::fs::read_to_string(p).ok()) { let log_lines: Vec<&str> = content.lines().collect(); let start = log_lines.len().saturating_sub(40); for line in &log_lines[start..] { lines.push(Line::raw(format!(" {}", line))); } } else { lines.push(Line::styled(" (no log available)", hint)); } let block = Block::default() .title_top(Line::from(screen_legend()).left_aligned()) .title_top(Line::from(format!(" {} ", name)).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); } }