rendering
This commit is contained in:
parent
36d698a3e1
commit
49cd6d6ab6
8 changed files with 178 additions and 157 deletions
|
|
@ -72,6 +72,7 @@ impl Agent {
|
||||||
started: std::time::Instant::now(),
|
started: std::time::Instant::now(),
|
||||||
expires_at: std::time::Instant::now() + std::time::Duration::from_secs(3600),
|
expires_at: std::time::Instant::now() + std::time::Duration::from_secs(3600),
|
||||||
});
|
});
|
||||||
|
self.changed.notify_one();
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,6 +86,7 @@ impl Agent {
|
||||||
started: std::time::Instant::now(),
|
started: std::time::Instant::now(),
|
||||||
expires_at: std::time::Instant::now() + ACTIVITY_LINGER,
|
expires_at: std::time::Instant::now() + ACTIVITY_LINGER,
|
||||||
});
|
});
|
||||||
|
self.changed.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove expired activities.
|
/// Remove expired activities.
|
||||||
|
|
@ -179,6 +181,8 @@ pub struct Agent {
|
||||||
pub agent_cycles: crate::subconscious::subconscious::AgentCycleState,
|
pub agent_cycles: crate::subconscious::subconscious::AgentCycleState,
|
||||||
/// Shared active tools — Agent writes, TUI reads.
|
/// Shared active tools — Agent writes, TUI reads.
|
||||||
pub active_tools: tools::SharedActiveTools,
|
pub active_tools: tools::SharedActiveTools,
|
||||||
|
/// Fires when agent state changes — UI wakes on this instead of polling.
|
||||||
|
pub changed: Arc<tokio::sync::Notify>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_journal(entries: &[context::JournalEntry]) -> String {
|
fn render_journal(entries: &[context::JournalEntry]) -> String {
|
||||||
|
|
@ -237,6 +241,7 @@ impl Agent {
|
||||||
generation: 0,
|
generation: 0,
|
||||||
agent_cycles,
|
agent_cycles,
|
||||||
active_tools,
|
active_tools,
|
||||||
|
changed: Arc::new(tokio::sync::Notify::new()),
|
||||||
};
|
};
|
||||||
|
|
||||||
agent.load_startup_journal();
|
agent.load_startup_journal();
|
||||||
|
|
@ -292,6 +297,7 @@ impl Agent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.context.entries.push(entry);
|
self.context.entries.push(entry);
|
||||||
|
self.changed.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Append streaming text to the last entry (creating a partial
|
/// Append streaming text to the last entry (creating a partial
|
||||||
|
|
@ -301,6 +307,7 @@ impl Agent {
|
||||||
let msg = entry.message_mut();
|
let msg = entry.message_mut();
|
||||||
if msg.role == Role::Assistant {
|
if msg.role == Role::Assistant {
|
||||||
msg.append_content(text);
|
msg.append_content(text);
|
||||||
|
self.changed.notify_one();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -308,6 +315,7 @@ impl Agent {
|
||||||
self.context.entries.push(ConversationEntry::Message(
|
self.context.entries.push(ConversationEntry::Message(
|
||||||
Message::assistant(text),
|
Message::assistant(text),
|
||||||
));
|
));
|
||||||
|
self.changed.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn budget(&self) -> ContextBudget {
|
pub fn budget(&self) -> ContextBudget {
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,7 @@ impl Mind {
|
||||||
// Restore conversation
|
// Restore conversation
|
||||||
let mut ag = self.agent.lock().await;
|
let mut ag = self.agent.lock().await;
|
||||||
ag.restore_from_log();
|
ag.restore_from_log();
|
||||||
|
ag.changed.notify_one();
|
||||||
drop(ag);
|
drop(ag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -760,11 +760,14 @@ impl ScreenView for InteractScreen {
|
||||||
fn label(&self) -> &'static str { "interact" }
|
fn label(&self) -> &'static str { "interact" }
|
||||||
|
|
||||||
fn tick(&mut self, frame: &mut Frame, area: Rect,
|
fn tick(&mut self, frame: &mut Frame, area: Rect,
|
||||||
key: Option<KeyEvent>, app: &mut App) -> Option<ScreenAction> {
|
events: &[ratatui::crossterm::event::Event], app: &mut App) {
|
||||||
// Handle keys
|
use ratatui::crossterm::event::Event;
|
||||||
if let Some(key) = key {
|
// Handle events
|
||||||
|
for event in events {
|
||||||
|
match event {
|
||||||
|
Event::Key(key) if key.kind == ratatui::crossterm::event::KeyEventKind::Press => {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Esc => return Some(ScreenAction::Hotkey(HotkeyAction::Interrupt)),
|
KeyCode::Esc => { let _ = self.mind_tx.send(crate::mind::MindCommand::Interrupt); }
|
||||||
KeyCode::Enter if !key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::SHIFT) => {
|
KeyCode::Enter if !key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::SHIFT) => {
|
||||||
let input: String = self.textarea.lines().join("\n");
|
let input: String = self.textarea.lines().join("\n");
|
||||||
if !input.is_empty() {
|
if !input.is_empty() {
|
||||||
|
|
@ -809,7 +812,11 @@ impl ScreenView for InteractScreen {
|
||||||
ActivePane::Conversation => ActivePane::Autonomous,
|
ActivePane::Conversation => ActivePane::Autonomous,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
_ => { self.textarea.input(key); }
|
_ => { self.textarea.input(*key); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Mouse(mouse) => { self.handle_mouse(*mouse); }
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -835,7 +842,6 @@ impl ScreenView for InteractScreen {
|
||||||
|
|
||||||
// Draw
|
// Draw
|
||||||
self.draw_main(frame, area, app);
|
self.draw_main(frame, area, app);
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use ratatui::{
|
||||||
crossterm::event::{KeyCode, KeyEvent},
|
crossterm::event::{KeyCode, KeyEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{App, ScreenAction, ScreenView, screen_legend};
|
use super::{App, ScreenView, screen_legend};
|
||||||
use crate::agent::context::ContextSection;
|
use crate::agent::context::ContextSection;
|
||||||
|
|
||||||
pub(crate) struct ConsciousScreen {
|
pub(crate) struct ConsciousScreen {
|
||||||
|
|
@ -117,13 +117,14 @@ impl ScreenView for ConsciousScreen {
|
||||||
fn label(&self) -> &'static str { "conscious" }
|
fn label(&self) -> &'static str { "conscious" }
|
||||||
|
|
||||||
fn tick(&mut self, frame: &mut Frame, area: Rect,
|
fn tick(&mut self, frame: &mut Frame, area: Rect,
|
||||||
key: Option<KeyEvent>, app: &mut App) -> Option<ScreenAction> {
|
events: &[ratatui::crossterm::event::Event], app: &mut App) {
|
||||||
// Handle keys
|
for event in events {
|
||||||
if let Some(key) = key {
|
if let ratatui::crossterm::event::Event::Key(key) = event {
|
||||||
let context_state = self.read_context_state(app);
|
if key.kind != ratatui::crossterm::event::KeyEventKind::Press { continue; }
|
||||||
let item_count = self.item_count(&context_state);
|
let context_state = self.read_context_state(app);
|
||||||
|
let item_count = self.item_count(&context_state);
|
||||||
|
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Up => {
|
KeyCode::Up => {
|
||||||
self.selected = Some(self.selected.unwrap_or(0).saturating_sub(1));
|
self.selected = Some(self.selected.unwrap_or(0).saturating_sub(1));
|
||||||
self.scroll_to_selected(item_count);
|
self.scroll_to_selected(item_count);
|
||||||
|
|
@ -145,9 +146,9 @@ impl ScreenView for ConsciousScreen {
|
||||||
}
|
}
|
||||||
KeyCode::PageUp => { self.scroll = self.scroll.saturating_sub(20); }
|
KeyCode::PageUp => { self.scroll = self.scroll.saturating_sub(20); }
|
||||||
KeyCode::PageDown => { self.scroll += 20; }
|
KeyCode::PageDown => { self.scroll += 20; }
|
||||||
KeyCode::Esc => return Some(ScreenAction::Switch(0)),
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw
|
// Draw
|
||||||
|
|
@ -214,6 +215,5 @@ impl ScreenView for ConsciousScreen {
|
||||||
.scroll((self.scroll, 0));
|
.scroll((self.scroll, 0));
|
||||||
|
|
||||||
frame.render_widget(para, area);
|
frame.render_widget(para, area);
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
253
src/user/mod.rs
253
src/user/mod.rs
|
|
@ -10,8 +10,8 @@ pub mod unconscious;
|
||||||
pub mod thalamus;
|
pub mod thalamus;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use ratatui::crossterm::event::{Event, EventStream, KeyEventKind};
|
use ratatui::crossterm::event::{Event, KeyEventKind};
|
||||||
use futures::StreamExt;
|
use std::io::Write;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::mind::MindCommand;
|
use crate::mind::MindCommand;
|
||||||
|
|
@ -88,7 +88,7 @@ pub enum ScreenAction {
|
||||||
/// A screen that can draw itself and handle input.
|
/// A screen that can draw itself and handle input.
|
||||||
pub(crate) trait ScreenView: Send {
|
pub(crate) trait ScreenView: Send {
|
||||||
fn tick(&mut self, frame: &mut ratatui::Frame, area: ratatui::layout::Rect,
|
fn tick(&mut self, frame: &mut ratatui::Frame, area: ratatui::layout::Rect,
|
||||||
key: Option<ratatui::crossterm::event::KeyEvent>, app: &mut App) -> Option<ScreenAction>;
|
events: &[ratatui::crossterm::event::Event], app: &mut App);
|
||||||
fn label(&self) -> &'static str;
|
fn label(&self) -> &'static str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,7 +128,6 @@ pub struct App {
|
||||||
pub(crate) active_tools: crate::agent::tools::SharedActiveTools,
|
pub(crate) active_tools: crate::agent::tools::SharedActiveTools,
|
||||||
pub should_quit: bool,
|
pub should_quit: bool,
|
||||||
pub submitted: Vec<String>,
|
pub submitted: Vec<String>,
|
||||||
pub hotkey_actions: Vec<HotkeyAction>,
|
|
||||||
pub(crate) context_info: Option<ContextInfo>,
|
pub(crate) context_info: Option<ContextInfo>,
|
||||||
pub(crate) shared_context: SharedContextState,
|
pub(crate) shared_context: SharedContextState,
|
||||||
pub(crate) agent_state: Vec<crate::subconscious::subconscious::AgentSnapshot>,
|
pub(crate) agent_state: Vec<crate::subconscious::subconscious::AgentSnapshot>,
|
||||||
|
|
@ -151,28 +150,13 @@ impl App {
|
||||||
top_p: 0.95,
|
top_p: 0.95,
|
||||||
top_k: 20,
|
top_k: 20,
|
||||||
active_tools,
|
active_tools,
|
||||||
should_quit: false, submitted: Vec::new(), hotkey_actions: Vec::new(),
|
should_quit: false, submitted: Vec::new(),
|
||||||
context_info: None, shared_context,
|
context_info: None, shared_context,
|
||||||
agent_state: Vec::new(),
|
agent_state: Vec::new(),
|
||||||
channel_status: Vec::new(), idle_info: None,
|
channel_status: Vec::new(), idle_info: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_global_key_old(&mut self, _key: KeyEvent) -> bool { false } // placeholder
|
|
||||||
|
|
||||||
/// Handle global keys only (Ctrl combos).
|
|
||||||
pub fn handle_global_key(&mut self, key: KeyEvent) -> bool {
|
|
||||||
if key.modifiers.contains(KeyModifiers::CONTROL) {
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Char('c') => { self.should_quit = true; return true; }
|
|
||||||
KeyCode::Char('r') => { self.hotkey_actions.push(HotkeyAction::CycleReasoning); return true; }
|
|
||||||
KeyCode::Char('k') => { self.hotkey_actions.push(HotkeyAction::KillProcess); return true; }
|
|
||||||
KeyCode::Char('p') => { self.hotkey_actions.push(HotkeyAction::CycleAutonomy); return true; }
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_channel_status(&mut self, channels: Vec<(String, bool, u32)>) {
|
pub fn set_channel_status(&mut self, channels: Vec<(String, bool, u32)>) {
|
||||||
self.channel_status = channels.into_iter()
|
self.channel_status = channels.into_iter()
|
||||||
|
|
@ -314,6 +298,20 @@ fn hotkey_adjust_sampling(mind: &crate::mind::Mind, param: usize, delta: f32) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if this is an event the main loop handles (F-keys, Ctrl combos, resize).
|
||||||
|
fn is_global_event(event: &ratatui::crossterm::event::Event) -> bool {
|
||||||
|
use ratatui::crossterm::event::{Event, KeyCode, KeyModifiers, KeyEventKind};
|
||||||
|
|
||||||
|
match event {
|
||||||
|
Event::Key(key) => {
|
||||||
|
if key.kind != KeyEventKind::Press { return false; }
|
||||||
|
matches!(key.code, KeyCode::F(_))
|
||||||
|
|| key.modifiers.contains(KeyModifiers::CONTROL)
|
||||||
|
}
|
||||||
|
Event::Resize(_, _) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn diff_mind_state(
|
fn diff_mind_state(
|
||||||
cur: &crate::mind::MindState,
|
cur: &crate::mind::MindState,
|
||||||
|
|
@ -373,23 +371,38 @@ pub async fn run(
|
||||||
tui::set_screen_legend(tui::screen_legend_from(&interact, &screens));
|
tui::set_screen_legend(tui::screen_legend_from(&interact, &screens));
|
||||||
|
|
||||||
let mut terminal = tui::init_terminal()?;
|
let mut terminal = tui::init_terminal()?;
|
||||||
let mut reader = EventStream::new();
|
|
||||||
|
|
||||||
let mut render_interval = tokio::time::interval(Duration::from_millis(50));
|
// Terminal event reader — dedicated thread reads sync, pushes to channel
|
||||||
render_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
let (event_tx, mut event_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
loop {
|
||||||
|
match crossterm::event::read() {
|
||||||
|
Ok(event) => { if event_tx.send(event).is_err() { break; } }
|
||||||
|
Err(_) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let agent_changed = agent.lock().await.changed.clone();
|
||||||
|
let mut turn_watch = mind.turn_watch();
|
||||||
let mut dirty = true;
|
let mut dirty = true;
|
||||||
let mut prev_mind = shared_mind.lock().unwrap().clone();
|
let mut prev_mind = shared_mind.lock().unwrap().clone();
|
||||||
let mut pending_key: Option<ratatui::crossterm::event::KeyEvent> = None;
|
let mut pending: Vec<ratatui::crossterm::event::Event> = Vec::new();
|
||||||
|
|
||||||
terminal.hide_cursor()?;
|
terminal.hide_cursor()?;
|
||||||
|
|
||||||
if let Ok(mut ag) = agent.try_lock() { ag.notify("consciousness v0.3"); }
|
if let Ok(mut ag) = agent.try_lock() { ag.notify("consciousness v0.3"); }
|
||||||
|
|
||||||
// Initial render
|
// Initial render
|
||||||
terminal.draw(|f| {
|
{
|
||||||
let area = f.area();
|
let mut frame = terminal.get_frame();
|
||||||
interact.tick(f, area, None, &mut app);
|
let area = frame.area();
|
||||||
})?;
|
interact.tick(&mut frame, area, &[], &mut app);
|
||||||
|
drop(frame);
|
||||||
|
terminal.flush()?;
|
||||||
|
terminal.swap_buffers();
|
||||||
|
terminal.backend_mut().flush()?;
|
||||||
|
}
|
||||||
|
|
||||||
// Replay conversation after Mind init completes (non-blocking check)
|
// Replay conversation after Mind init completes (non-blocking check)
|
||||||
let mut startup_done = false;
|
let mut startup_done = false;
|
||||||
|
|
@ -398,122 +411,114 @@ pub async fn run(
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
biased;
|
biased;
|
||||||
|
|
||||||
maybe_event = reader.next() => {
|
Some(event) = event_rx.recv() => {
|
||||||
match maybe_event {
|
pending.push(event);
|
||||||
Some(Ok(Event::Key(key))) => {
|
while let Ok(event) = event_rx.try_recv() {
|
||||||
if key.kind != KeyEventKind::Press { continue; }
|
pending.push(event);
|
||||||
idle_state.user_activity();
|
|
||||||
|
|
||||||
// F-keys switch screens
|
|
||||||
if let ratatui::crossterm::event::KeyCode::F(n) = key.code {
|
|
||||||
active_screen = match n {
|
|
||||||
1 => 0, // interact
|
|
||||||
n @ 2..=5 if (n as usize - 2) < screens.len() => n as usize - 1,
|
|
||||||
_ => active_screen,
|
|
||||||
};
|
|
||||||
if active_screen == 4 { // thalamus — refresh channels
|
|
||||||
let tx = channel_tx.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let result = crate::thalamus::channels::fetch_all_channels().await;
|
|
||||||
let _ = tx.send(result).await;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
dirty = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Global keys (Ctrl combos) — only pass to screen if not consumed
|
|
||||||
if !app.handle_global_key(key) {
|
|
||||||
pending_key = Some(key);
|
|
||||||
}
|
|
||||||
dirty = true;
|
|
||||||
}
|
|
||||||
Some(Ok(Event::Mouse(_mouse))) => {
|
|
||||||
// TODO: route to active screen
|
|
||||||
dirty = true;
|
|
||||||
}
|
|
||||||
Some(Ok(Event::Resize(_w, _h))) => {
|
|
||||||
terminal.clear()?;
|
|
||||||
dirty = true;
|
|
||||||
}
|
|
||||||
Some(Err(_)) => break,
|
|
||||||
None => break,
|
|
||||||
_ => continue,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = agent_changed.notified() => {
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
_ = render_interval.tick() => {
|
_ = turn_watch.changed() => {
|
||||||
idle_state.decay_ewma();
|
dirty = true;
|
||||||
app.update_idle(&idle_state);
|
|
||||||
|
|
||||||
// One-time: mark startup done after Mind init
|
|
||||||
if !startup_done {
|
|
||||||
if let Ok(mut ag) = agent.try_lock() {
|
|
||||||
let model = ag.model().to_string();
|
|
||||||
ag.notify(format!("model: {}", model));
|
|
||||||
startup_done = true;
|
|
||||||
dirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Diff MindState — generate UI messages from changes
|
|
||||||
{
|
|
||||||
let cur = shared_mind.lock().unwrap();
|
|
||||||
diff_mind_state(&cur, &prev_mind, &mut dirty);
|
|
||||||
prev_mind = cur.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
while let Ok(notif) = notify_rx.try_recv() {
|
|
||||||
let tx = channel_tx.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let result = crate::thalamus::channels::fetch_all_channels().await;
|
|
||||||
let _ = tx.send(result).await;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(channels) = channel_rx.recv() => {
|
Some(channels) = channel_rx.recv() => {
|
||||||
app.set_channel_status(channels);
|
app.set_channel_status(channels);
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle hotkey actions
|
// State sync on every wake
|
||||||
let actions: Vec<HotkeyAction> = app.hotkey_actions.drain(..).collect();
|
idle_state.decay_ewma();
|
||||||
for action in actions {
|
app.update_idle(&idle_state);
|
||||||
match action {
|
if !startup_done {
|
||||||
HotkeyAction::CycleReasoning => hotkey_cycle_reasoning(mind),
|
if let Ok(mut ag) = agent.try_lock() {
|
||||||
HotkeyAction::KillProcess => hotkey_kill_processes(mind).await,
|
let model = ag.model().to_string();
|
||||||
HotkeyAction::Interrupt => { let _ = mind_tx.send(MindCommand::Interrupt); }
|
ag.notify(format!("model: {}", model));
|
||||||
HotkeyAction::CycleAutonomy => hotkey_cycle_autonomy(mind),
|
startup_done = true;
|
||||||
HotkeyAction::AdjustSampling(param, delta) => hotkey_adjust_sampling(mind, param, delta),
|
dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let cur = shared_mind.lock().unwrap();
|
||||||
|
diff_mind_state(&cur, &prev_mind, &mut dirty);
|
||||||
|
prev_mind = cur.clone();
|
||||||
|
}
|
||||||
|
while let Ok(_notif) = notify_rx.try_recv() {
|
||||||
|
let tx = channel_tx.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let result = crate::thalamus::channels::fetch_all_channels().await;
|
||||||
|
let _ = tx.send(result).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pending.is_empty() { idle_state.user_activity(); }
|
||||||
|
|
||||||
|
while !pending.is_empty() {
|
||||||
|
let global_pos = pending.iter().position(|e| is_global_event(e))
|
||||||
|
.unwrap_or(pending.len());
|
||||||
|
|
||||||
|
if global_pos > 0 {
|
||||||
|
let mut frame = terminal.get_frame();
|
||||||
|
let area = frame.area();
|
||||||
|
if active_screen == 0 {
|
||||||
|
interact.tick(&mut frame, area, &pending[..global_pos], &mut app);
|
||||||
|
} else {
|
||||||
|
screens[active_screen - 1].tick(&mut frame, area, &pending[..global_pos], &mut app);
|
||||||
|
}
|
||||||
|
drop(frame);
|
||||||
|
terminal.flush()?;
|
||||||
|
terminal.swap_buffers();
|
||||||
|
terminal.backend_mut().flush()?;
|
||||||
|
dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pending = pending.split_off(global_pos);
|
||||||
|
if pending.is_empty() { break; }
|
||||||
|
|
||||||
|
// Global event is first — handle it
|
||||||
|
use ratatui::crossterm::event::{Event, KeyCode, KeyModifiers};
|
||||||
|
let event = pending.remove(0);
|
||||||
|
match event {
|
||||||
|
Event::Key(key) => {
|
||||||
|
if let KeyCode::F(n) = key.code {
|
||||||
|
active_screen = match n {
|
||||||
|
1 => 0,
|
||||||
|
n @ 2..=5 if (n as usize - 2) < screens.len() => n as usize - 1,
|
||||||
|
_ => active_screen,
|
||||||
|
};
|
||||||
|
} else if key.modifiers.contains(KeyModifiers::CONTROL) {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Char('c') => { app.should_quit = true; }
|
||||||
|
KeyCode::Char('r') => hotkey_cycle_reasoning(mind),
|
||||||
|
KeyCode::Char('k') => hotkey_kill_processes(mind).await,
|
||||||
|
KeyCode::Char('p') => hotkey_cycle_autonomy(mind),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Resize(_, _) => { let _ = terminal.clear(); }
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if dirty {
|
if dirty {
|
||||||
let key = pending_key.take();
|
let mut frame = terminal.get_frame();
|
||||||
let mut screen_action = None;
|
let area = frame.area();
|
||||||
if active_screen == 0 {
|
if active_screen == 0 {
|
||||||
terminal.draw(|f| {
|
interact.tick(&mut frame, area, &[], &mut app);
|
||||||
let area = f.area();
|
|
||||||
screen_action = interact.tick(f, area, key, &mut app);
|
|
||||||
})?;
|
|
||||||
} else {
|
} else {
|
||||||
let screen = &mut screens[active_screen - 1];
|
screens[active_screen - 1].tick(&mut frame, area, &[], &mut app);
|
||||||
terminal.draw(|f| {
|
|
||||||
let area = f.area();
|
|
||||||
screen_action = screen.tick(f, area, key, &mut app);
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
if let Some(action) = screen_action {
|
|
||||||
match action {
|
|
||||||
tui::ScreenAction::Switch(i) => { active_screen = i; dirty = true; continue; }
|
|
||||||
tui::ScreenAction::Hotkey(h) => app.hotkey_actions.push(h),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
drop(frame);
|
||||||
|
terminal.flush()?;
|
||||||
|
terminal.swap_buffers();
|
||||||
|
terminal.backend_mut().flush()?;
|
||||||
dirty = false;
|
dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,11 @@ impl ScreenView for SubconsciousScreen {
|
||||||
fn label(&self) -> &'static str { "subconscious" }
|
fn label(&self) -> &'static str { "subconscious" }
|
||||||
|
|
||||||
fn tick(&mut self, frame: &mut Frame, area: Rect,
|
fn tick(&mut self, frame: &mut Frame, area: Rect,
|
||||||
key: Option<KeyEvent>, app: &mut App) -> Option<ScreenAction> {
|
events: &[ratatui::crossterm::event::Event], app: &mut App) {
|
||||||
// Handle keys
|
// Handle keys
|
||||||
if let Some(key) = key {
|
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 {
|
match key.code {
|
||||||
KeyCode::Up if !self.log_view => {
|
KeyCode::Up if !self.log_view => {
|
||||||
self.selected = self.selected.saturating_sub(1);
|
self.selected = self.selected.saturating_sub(1);
|
||||||
|
|
@ -44,11 +46,11 @@ impl ScreenView for SubconsciousScreen {
|
||||||
KeyCode::Esc | KeyCode::Left if self.log_view => {
|
KeyCode::Esc | KeyCode::Left if self.log_view => {
|
||||||
self.log_view = false;
|
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::PageUp => { self.scroll = self.scroll.saturating_sub(20); }
|
||||||
KeyCode::PageDown => { self.scroll += 20; }
|
KeyCode::PageDown => { self.scroll += 20; }
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw
|
// Draw
|
||||||
|
|
@ -57,7 +59,6 @@ impl ScreenView for SubconsciousScreen {
|
||||||
} else {
|
} else {
|
||||||
self.draw_list(frame, area, app);
|
self.draw_list(frame, area, app);
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,11 @@ impl ScreenView for ThalamusScreen {
|
||||||
fn label(&self) -> &'static str { "thalamus" }
|
fn label(&self) -> &'static str { "thalamus" }
|
||||||
|
|
||||||
fn tick(&mut self, frame: &mut Frame, area: Rect,
|
fn tick(&mut self, frame: &mut Frame, area: Rect,
|
||||||
key: Option<KeyEvent>, app: &mut App) -> Option<ScreenAction> {
|
events: &[ratatui::crossterm::event::Event], app: &mut App) {
|
||||||
// Handle keys
|
// Handle keys
|
||||||
if let Some(key) = key {
|
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 {
|
match key.code {
|
||||||
KeyCode::Up => { self.sampling_selected = self.sampling_selected.saturating_sub(1); }
|
KeyCode::Up => { self.sampling_selected = self.sampling_selected.saturating_sub(1); }
|
||||||
KeyCode::Down => { self.sampling_selected = (self.sampling_selected + 1).min(2); }
|
KeyCode::Down => { self.sampling_selected = (self.sampling_selected + 1).min(2); }
|
||||||
|
|
@ -36,17 +38,15 @@ impl ScreenView for ThalamusScreen {
|
||||||
let delta = match self.sampling_selected {
|
let delta = match self.sampling_selected {
|
||||||
0 => 0.05, 1 => 0.05, 2 => 5.0, _ => 0.0,
|
0 => 0.05, 1 => 0.05, 2 => 5.0, _ => 0.0,
|
||||||
};
|
};
|
||||||
return Some(ScreenAction::Hotkey(HotkeyAction::AdjustSampling(self.sampling_selected, delta)));
|
|
||||||
}
|
}
|
||||||
KeyCode::Left => {
|
KeyCode::Left => {
|
||||||
let delta = match self.sampling_selected {
|
let delta = match self.sampling_selected {
|
||||||
0 => -0.05, 1 => -0.05, 2 => -5.0, _ => 0.0,
|
0 => -0.05, 1 => -0.05, 2 => -5.0, _ => 0.0,
|
||||||
};
|
};
|
||||||
return Some(ScreenAction::Hotkey(HotkeyAction::AdjustSampling(self.sampling_selected, delta)));
|
|
||||||
}
|
}
|
||||||
KeyCode::Esc => return Some(ScreenAction::Switch(0)),
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw
|
// Draw
|
||||||
|
|
@ -131,6 +131,5 @@ impl ScreenView for ThalamusScreen {
|
||||||
.scroll((self.scroll, 0));
|
.scroll((self.scroll, 0));
|
||||||
|
|
||||||
frame.render_widget(para, area);
|
frame.render_widget(para, area);
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,13 +38,15 @@ impl ScreenView for UnconsciousScreen {
|
||||||
fn label(&self) -> &'static str { "unconscious" }
|
fn label(&self) -> &'static str { "unconscious" }
|
||||||
|
|
||||||
fn tick(&mut self, frame: &mut Frame, area: Rect,
|
fn tick(&mut self, frame: &mut Frame, area: Rect,
|
||||||
key: Option<KeyEvent>, _app: &mut App) -> Option<ScreenAction> {
|
events: &[ratatui::crossterm::event::Event], _app: &mut App) {
|
||||||
if let Some(key) = key {
|
for event in events {
|
||||||
match key.code {
|
if let ratatui::crossterm::event::Event::Key(key) = event {
|
||||||
KeyCode::PageUp => { self.scroll = self.scroll.saturating_sub(20); }
|
if key.kind != ratatui::crossterm::event::KeyEventKind::Press { continue; }
|
||||||
KeyCode::PageDown => { self.scroll += 20; }
|
match key.code {
|
||||||
KeyCode::Esc => return Some(ScreenAction::Switch(0)),
|
KeyCode::PageUp => { self.scroll = self.scroll.saturating_sub(20); }
|
||||||
_ => {}
|
KeyCode::PageDown => { self.scroll += 20; }
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,7 +81,6 @@ impl ScreenView for UnconsciousScreen {
|
||||||
render_tasks(frame, &st.tasks, tasks_area);
|
render_tasks(frame, &st.tasks, tasks_area);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue