ralph_tui/
lib.rs

1//! # ralph-tui
2//!
3//! Terminal user interface for the Ralph Orchestrator framework.
4//!
5//! Built with `ratatui` and `crossterm`, this crate provides:
6//! - Interactive terminal UI for monitoring agent orchestration
7//! - Real-time display of agent messages and state
8//! - Keyboard navigation and input handling
9
10mod app;
11pub mod input;
12pub mod scroll;
13pub mod state;
14pub mod widgets;
15
16use anyhow::Result;
17use app::App;
18use crossterm::event::{KeyCode, KeyModifiers};
19use ralph_adapters::pty_handle::PtyHandle;
20use ralph_proto::{Event, HatId};
21use std::collections::HashMap;
22use std::sync::{Arc, Mutex};
23
24pub use state::{LoopMode, TuiState};
25pub use widgets::terminal::TerminalWidget;
26pub use widgets::{footer, header};
27
28/// Main TUI handle that integrates with the event bus.
29pub struct Tui {
30    state: Arc<Mutex<TuiState>>,
31    pty_handle: Option<PtyHandle>,
32    prefix_key: KeyCode,
33    prefix_modifiers: KeyModifiers,
34}
35
36impl Tui {
37    /// Creates a new TUI instance with shared state.
38    pub fn new() -> Self {
39        Self {
40            state: Arc::new(Mutex::new(TuiState::new())),
41            pty_handle: None,
42            prefix_key: KeyCode::Char('a'),
43            prefix_modifiers: KeyModifiers::CONTROL,
44        }
45    }
46
47    /// Sets custom prefix key.
48    #[must_use]
49    pub fn with_prefix(mut self, prefix_key: KeyCode, prefix_modifiers: KeyModifiers) -> Self {
50        self.prefix_key = prefix_key;
51        self.prefix_modifiers = prefix_modifiers;
52        self
53    }
54
55    /// Sets the PTY handle for terminal output.
56    #[must_use]
57    pub fn with_pty(mut self, pty_handle: PtyHandle) -> Self {
58        self.pty_handle = Some(pty_handle);
59        self
60    }
61
62    /// Sets the hat map for dynamic topic-to-hat resolution.
63    ///
64    /// This allows the TUI to display the correct hat for custom topics
65    /// without hardcoding them in TuiState::update().
66    #[must_use]
67    pub fn with_hat_map(self, hat_map: HashMap<String, (HatId, String)>) -> Self {
68        if let Ok(mut state) = self.state.lock() {
69            *state = TuiState::with_hat_map(hat_map);
70        }
71        self
72    }
73
74    /// Returns an observer closure that updates TUI state from events.
75    pub fn observer(&self) -> impl Fn(&Event) + Send + 'static {
76        let state = Arc::clone(&self.state);
77        move |event: &Event| {
78            if let Ok(mut s) = state.lock() {
79                s.update(event);
80            }
81        }
82    }
83
84    /// Runs the TUI application loop.
85    ///
86    /// # Panics
87    ///
88    /// Panics if `with_pty()` was not called before running.
89    ///
90    /// # Errors
91    ///
92    /// Returns an error if the terminal cannot be initialized or
93    /// if the application loop encounters an unrecoverable error.
94    pub async fn run(self) -> Result<()> {
95        let pty_handle = self
96            .pty_handle
97            .expect("PTY handle not set - call with_pty() first");
98        let app = App::with_prefix(
99            Arc::clone(&self.state),
100            pty_handle,
101            self.prefix_key,
102            self.prefix_modifiers,
103        );
104        app.run().await
105    }
106}
107
108impl Default for Tui {
109    fn default() -> Self {
110        Self::new()
111    }
112}