An AI agent built to do Ralph loops - plan mode for planning and ralph mode for implementing.
at main 99 lines 2.6 kB view raw
1mod app; 2pub mod messages; 3mod ui; 4pub mod views; 5pub mod widgets; 6 7pub use messages::{AgentMessage, AgentReceiver, AgentSender, agent_channel}; 8 9pub use app::{ActiveTab, App}; 10pub use ui::draw; 11 12use crossterm::{ 13 event::{self, Event, KeyEventKind}, 14 execute, 15 terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}, 16}; 17use ratatui::{Terminal, backend::CrosstermBackend}; 18use std::io; 19use std::panic; 20use std::time::Duration; 21use tokio::sync::mpsc::Receiver; 22 23pub type Tui = Terminal<CrosstermBackend<io::Stdout>>; 24 25pub fn setup_terminal() -> io::Result<Tui> { 26 enable_raw_mode()?; 27 let mut stdout = io::stdout(); 28 execute!(stdout, EnterAlternateScreen)?; 29 let backend = CrosstermBackend::new(stdout); 30 Terminal::new(backend) 31} 32 33pub fn restore_terminal(terminal: &mut Tui) -> io::Result<()> { 34 disable_raw_mode()?; 35 execute!(terminal.backend_mut(), LeaveAlternateScreen)?; 36 terminal.show_cursor()?; 37 Ok(()) 38} 39 40/// Run the TUI event loop with panic safety. 41pub async fn run( 42 terminal: &mut Tui, 43 app: &mut App, 44 agent_rx: &mut Receiver<AgentMessage>, 45) -> anyhow::Result<()> { 46 let original_hook = panic::take_hook(); 47 panic::set_hook(Box::new(|info| { 48 let _ = disable_raw_mode(); 49 let _ = execute!(io::stdout(), LeaveAlternateScreen); 50 eprintln!("Panic: {}", info); 51 })); 52 53 let result = run_loop(terminal, app, agent_rx).await; 54 55 // Restore original panic hook 56 let _ = panic::take_hook(); 57 panic::set_hook(original_hook); 58 59 result 60} 61 62async fn run_loop( 63 terminal: &mut Tui, 64 app: &mut App, 65 agent_rx: &mut Receiver<AgentMessage>, 66) -> anyhow::Result<()> { 67 let mut interval = tokio::time::interval(Duration::from_millis(50)); 68 69 loop { 70 if !app.running { 71 break; 72 } 73 74 terminal.draw(|frame| crate::tui::ui::draw(frame, app))?; 75 76 tokio::select! { 77 _ = interval.tick() => { 78 app.spinner.tick(); 79 80 while event::poll(Duration::ZERO)? { 81 match event::read()? { 82 Event::Key(key) if key.kind == KeyEventKind::Press => { 83 app.handle_key(key); 84 } 85 Event::Resize(_, _) => { 86 // Redraw handled next iteration 87 } 88 _ => {} 89 } 90 } 91 } 92 93 Some(msg) = agent_rx.recv() => { 94 app.handle_agent_message(msg); 95 } 96 } 97 } 98 Ok(()) 99}