oxigaf_cli/
interactive.rs1use crossterm::{
7 event::{self, Event, KeyCode, KeyEvent},
8 terminal::{disable_raw_mode, enable_raw_mode},
9};
10use std::sync::{
11 atomic::{AtomicBool, AtomicU8, Ordering},
12 Arc,
13};
14use std::time::Duration;
15
16pub struct InteractiveController {
21 pub paused: Arc<AtomicBool>,
23 pub lr_adjustment: Arc<AtomicU8>,
25 pub verbose_toggle: Arc<AtomicBool>,
27 pub save_requested: Arc<AtomicBool>,
29 pub quit_requested: Arc<AtomicBool>,
31}
32
33impl InteractiveController {
34 #[must_use]
36 pub fn new() -> Self {
37 Self {
38 paused: Arc::new(AtomicBool::new(false)),
39 lr_adjustment: Arc::new(AtomicU8::new(100)), verbose_toggle: Arc::new(AtomicBool::new(false)),
41 save_requested: Arc::new(AtomicBool::new(false)),
42 quit_requested: Arc::new(AtomicBool::new(false)),
43 }
44 }
45
46 pub fn start_keyboard_listener(&self) {
56 let paused = Arc::clone(&self.paused);
57 let lr_adj = Arc::clone(&self.lr_adjustment);
58 let verbose = Arc::clone(&self.verbose_toggle);
59 let save = Arc::clone(&self.save_requested);
60 let quit = Arc::clone(&self.quit_requested);
61
62 std::thread::spawn(move || {
63 if enable_raw_mode().is_err() {
65 tracing::error!("Failed to enable terminal raw mode for interactive controls");
66 return;
67 }
68
69 loop {
70 match event::poll(Duration::from_millis(100)) {
72 Ok(true) => {
73 match event::read() {
75 Ok(Event::Key(key_event)) => {
76 handle_key_event(
77 key_event, &paused, &lr_adj, &verbose, &save, &quit,
78 );
79 }
80 Ok(_) => {} Err(e) => {
82 tracing::error!("Failed to read keyboard event: {}", e);
83 }
84 }
85 }
86 Ok(false) => {} Err(e) => {
88 tracing::error!("Failed to poll for keyboard events: {}", e);
89 }
90 }
91
92 if quit.load(Ordering::Relaxed) {
94 let _ = disable_raw_mode();
95 break;
96 }
97 }
98 });
99 }
100
101 pub fn print_controls(&self) {
103 println!();
104 println!("Interactive Controls:");
105 println!(" [Space] Pause/Resume training");
106 println!(" [Up/Down] Increase/Decrease learning rate");
107 println!(" [v] Toggle verbose logging");
108 println!(" [s] Save checkpoint now");
109 println!(" [q] Quit gracefully");
110 println!();
111 }
112}
113
114impl Default for InteractiveController {
115 fn default() -> Self {
116 Self::new()
117 }
118}
119
120fn handle_key_event(
122 key: KeyEvent,
123 paused: &Arc<AtomicBool>,
124 lr_adj: &Arc<AtomicU8>,
125 verbose: &Arc<AtomicBool>,
126 save: &Arc<AtomicBool>,
127 quit: &Arc<AtomicBool>,
128) {
129 match key.code {
130 KeyCode::Char(' ') => {
131 let was_paused = paused.fetch_xor(true, Ordering::Relaxed);
133 let now_paused = !was_paused;
134 if now_paused {
135 println!("\nTraining paused");
136 } else {
137 println!("\nTraining resumed");
138 }
139 }
140 KeyCode::Up => {
141 let current = lr_adj.load(Ordering::Relaxed);
143 let new = current.saturating_add(10).min(200);
144 lr_adj.store(new, Ordering::Relaxed);
145 println!("\nLearning rate: {:.1}x", f64::from(new) / 100.0);
146 }
147 KeyCode::Down => {
148 let current = lr_adj.load(Ordering::Relaxed);
150 let new = current.saturating_sub(10).max(10);
151 lr_adj.store(new, Ordering::Relaxed);
152 println!("\nLearning rate: {:.1}x", f64::from(new) / 100.0);
153 }
154 KeyCode::Char('v') | KeyCode::Char('V') => {
155 let was_verbose = verbose.fetch_xor(true, Ordering::Relaxed);
157 let now_verbose = !was_verbose;
158 println!("\nVerbose: {}", if now_verbose { "ON" } else { "OFF" });
159 }
160 KeyCode::Char('s') | KeyCode::Char('S') => {
161 save.store(true, Ordering::Relaxed);
163 println!("\nCheckpoint save requested...");
164 }
165 KeyCode::Char('q') | KeyCode::Char('Q') => {
166 quit.store(true, Ordering::Relaxed);
168 println!("\nGraceful shutdown requested...");
169 }
170 _ => {} }
172}