diff --git a/src/lib.rs b/src/lib.rs index 6785933..cb1157e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -#![feature(coroutines, coroutine_trait)] - // consciousness — unified crate for memory, agents, and subconscious processes // // thought/ — shared cognitive substrate (tools, context, memory ops) diff --git a/src/user/chat.rs b/src/user/chat.rs index 067f418..5b3bc79 100644 --- a/src/user/chat.rs +++ b/src/user/chat.rs @@ -9,15 +9,225 @@ use ratatui::{ text::{Line, Span}, widgets::{Block, Borders, Paragraph, Wrap}, Frame, + crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind, MouseButton}, }; -use super::{ActivePane, App, Marker, PaneState, screen_legend}; +use super::{ + ActivePane, App, HotkeyAction, Marker, PaneState, ScreenAction, ScreenView, + new_textarea, screen_legend, +}; +use crate::user::ui_channel::{UiMessage, StreamTarget}; -impl App { +pub(crate) struct InteractScreen { + pub(crate) autonomous: PaneState, + pub(crate) conversation: PaneState, + pub(crate) tools: PaneState, + pub(crate) textarea: tui_textarea::TextArea<'static>, + pub(crate) input_history: Vec, + pub(crate) history_index: Option, + pub(crate) active_pane: ActivePane, + pub(crate) pane_areas: [Rect; 3], + pub(crate) needs_assistant_marker: bool, + pub(crate) turn_started: Option, + pub(crate) call_started: Option, + pub(crate) call_timeout_secs: u64, +} + +impl InteractScreen { + pub fn new() -> Self { + Self { + autonomous: PaneState::new(true), + conversation: PaneState::new(true), + tools: PaneState::new(false), + textarea: new_textarea(vec![String::new()]), + input_history: Vec::new(), + history_index: None, + active_pane: ActivePane::Conversation, + pane_areas: [Rect::default(); 3], + needs_assistant_marker: false, + turn_started: None, + call_started: None, + call_timeout_secs: 60, + } + } + + /// Process a UiMessage — update pane state. + pub fn handle_ui_message(&mut self, msg: &UiMessage, app: &mut App) { + match msg { + UiMessage::TextDelta(text, target) => match target { + StreamTarget::Conversation => { + if self.needs_assistant_marker { + self.conversation.pending_marker = Marker::Assistant; + self.needs_assistant_marker = false; + } + self.conversation.current_color = Color::Reset; + self.conversation.append_text(text); + } + StreamTarget::Autonomous => { + self.autonomous.current_color = Color::Reset; + self.autonomous.append_text(text); + } + }, + UiMessage::UserInput(text) => { + self.conversation.push_line_with_marker(text.clone(), Color::Cyan, Marker::User); + self.turn_started = Some(std::time::Instant::now()); + self.needs_assistant_marker = true; + app.status.turn_tools = 0; + } + UiMessage::ToolCall { name, args_summary } => { + app.status.turn_tools += 1; + let line = if args_summary.is_empty() { format!("[{}]", name) } + else { format!("[{}] {}", name, args_summary) }; + self.tools.push_line(line, Color::Yellow); + } + UiMessage::ToolResult { result, .. } => { + for line in result.lines() { + self.tools.push_line(format!(" {}", line), Color::DarkGray); + } + self.tools.push_line(String::new(), Color::Reset); + } + UiMessage::DmnAnnotation(text) => { + self.autonomous.push_line(text.clone(), Color::Yellow); + self.turn_started = Some(std::time::Instant::now()); + self.needs_assistant_marker = true; + app.status.turn_tools = 0; + } + UiMessage::StatusUpdate(info) => { + if !info.dmn_state.is_empty() { + app.status.dmn_state = info.dmn_state.clone(); + app.status.dmn_turns = info.dmn_turns; + app.status.dmn_max_turns = info.dmn_max_turns; + } + if info.prompt_tokens > 0 { app.status.prompt_tokens = info.prompt_tokens; } + if !info.model.is_empty() { app.status.model = info.model.clone(); } + if !info.context_budget.is_empty() { app.status.context_budget = info.context_budget.clone(); } + } + UiMessage::Activity(text) => { + if text.is_empty() { + self.call_started = None; + } else if app.activity.is_empty() || self.call_started.is_none() { + self.call_started = Some(std::time::Instant::now()); + self.call_timeout_secs = crate::config::get().api_stream_timeout_secs; + } + app.activity = text.clone(); + } + UiMessage::Reasoning(text) => { + self.autonomous.current_color = Color::DarkGray; + self.autonomous.append_text(text); + } + UiMessage::Debug(text) => { + self.tools.push_line(format!("[debug] {}", text), Color::DarkGray); + } + UiMessage::Info(text) => { + self.conversation.push_line(text.clone(), Color::Cyan); + } + UiMessage::ContextInfoUpdate(info) => { app.context_info = Some(info.clone()); } + UiMessage::AgentUpdate(agents) => { app.agent_state = agents.clone(); } + _ => {} + } + } + + fn scroll_active_up(&mut self, n: u16) { + match self.active_pane { + ActivePane::Autonomous => self.autonomous.scroll_up(n), + ActivePane::Conversation => self.conversation.scroll_up(n), + ActivePane::Tools => self.tools.scroll_up(n), + } + } + + fn scroll_active_down(&mut self, n: u16) { + match self.active_pane { + ActivePane::Autonomous => self.autonomous.scroll_down(n), + ActivePane::Conversation => self.conversation.scroll_down(n), + ActivePane::Tools => self.tools.scroll_down(n), + } + } + + pub fn handle_mouse(&mut self, mouse: MouseEvent) { + match mouse.kind { + MouseEventKind::ScrollUp => self.scroll_active_up(3), + MouseEventKind::ScrollDown => self.scroll_active_down(3), + MouseEventKind::Down(MouseButton::Left) => { + let (x, y) = (mouse.column, mouse.row); + for (i, area) in self.pane_areas.iter().enumerate() { + if x >= area.x && x < area.x + area.width && y >= area.y && y < area.y + area.height { + self.active_pane = match i { 0 => ActivePane::Autonomous, 1 => ActivePane::Conversation, _ => ActivePane::Tools }; + break; + } + } + } + _ => {} + } + } +} + +impl ScreenView for InteractScreen { + fn label(&self) -> &'static str { "interact" } + + fn tick(&mut self, frame: &mut Frame, area: Rect, + key: Option, app: &mut App) -> Option { + // Handle keys + if let Some(key) = key { + match key.code { + KeyCode::Esc => return Some(ScreenAction::Hotkey(HotkeyAction::Interrupt)), + KeyCode::Enter if !key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::SHIFT) => { + let input: String = self.textarea.lines().join("\n"); + if !input.is_empty() { + if self.input_history.last().map_or(true, |h| h != &input) { + self.input_history.push(input.clone()); + } + self.history_index = None; + // TODO: push to submitted via app or return action + } + } + KeyCode::Up if key.modifiers.contains(KeyModifiers::CONTROL) => self.scroll_active_up(3), + KeyCode::Down if key.modifiers.contains(KeyModifiers::CONTROL) => self.scroll_active_down(3), + KeyCode::Up => { + if !self.input_history.is_empty() { + let idx = match self.history_index { None => self.input_history.len() - 1, Some(i) => i.saturating_sub(1) }; + self.history_index = Some(idx); + let mut ta = new_textarea(self.input_history[idx].lines().map(String::from).collect()); + ta.move_cursor(tui_textarea::CursorMove::End); + self.textarea = ta; + } + } + KeyCode::Down => { + if let Some(idx) = self.history_index { + if idx + 1 < self.input_history.len() { + self.history_index = Some(idx + 1); + let mut ta = new_textarea(self.input_history[idx + 1].lines().map(String::from).collect()); + ta.move_cursor(tui_textarea::CursorMove::End); + self.textarea = ta; + } else { + self.history_index = None; + self.textarea = new_textarea(vec![String::new()]); + } + } + } + KeyCode::PageUp => self.scroll_active_up(10), + KeyCode::PageDown => self.scroll_active_down(10), + KeyCode::Tab => { + self.active_pane = match self.active_pane { + ActivePane::Autonomous => ActivePane::Tools, + ActivePane::Tools => ActivePane::Conversation, + ActivePane::Conversation => ActivePane::Autonomous, + }; + } + _ => { self.textarea.input(key); } + } + } + + // Draw + self.draw_main(frame, area, app); + None + } +} + +impl InteractScreen { /// Draw the main (F1) screen — four-pane layout with status bar. - pub(crate) fn draw_main(&mut self, frame: &mut Frame, size: Rect) { + pub(crate) fn draw_main(&mut self, frame: &mut Frame, size: Rect, app: &App) { // Main layout: content area + active tools overlay + status bar - let active_tools = self.active_tools.lock().unwrap(); + let active_tools = app.active_tools.lock().unwrap(); let tool_lines = active_tools.len() as u16; let main_chunks = Layout::default() .direction(Direction::Vertical) @@ -124,47 +334,47 @@ impl App { } // Draw status bar with live activity indicator - let timer = if !self.activity.is_empty() { + let timer = if !app.activity.is_empty() { let total = self.turn_started.map(|t| t.elapsed().as_secs()).unwrap_or(0); let call = self.call_started.map(|t| t.elapsed().as_secs()).unwrap_or(0); format!(" {}s, {}/{}s", total, call, self.call_timeout_secs) } else { String::new() }; - let tools_info = if self.status.turn_tools > 0 { - format!(" ({}t)", self.status.turn_tools) + let tools_info = if app.status.turn_tools > 0 { + format!(" ({}t)", app.status.turn_tools) } else { String::new() }; - let activity_part = if self.activity.is_empty() { + let activity_part = if app.activity.is_empty() { String::new() } else { - format!(" | {}{}{}", self.activity, tools_info, timer) + format!(" | {}{}{}", app.activity, tools_info, timer) }; - let budget_part = if self.status.context_budget.is_empty() { + let budget_part = if app.status.context_budget.is_empty() { String::new() } else { - format!(" [{}]", self.status.context_budget) + format!(" [{}]", app.status.context_budget) }; let left_status = format!( " {} | {}/{} dmn | {}K tok in{}{}", - self.status.dmn_state, - self.status.dmn_turns, - self.status.dmn_max_turns, - self.status.prompt_tokens / 1000, + app.status.dmn_state, + app.status.dmn_turns, + app.status.dmn_max_turns, + app.status.prompt_tokens / 1000, budget_part, activity_part, ); - let proc_indicator = if self.running_processes > 0 { - format!(" {}proc", self.running_processes) + let proc_indicator = if app.running_processes > 0 { + format!(" {}proc", app.running_processes) } else { String::new() }; - let reason_indicator = if self.reasoning_effort != "none" { - format!(" reason:{}", self.reasoning_effort) + let reason_indicator = if app.reasoning_effort != "none" { + format!(" reason:{}", app.reasoning_effort) } else { String::new() }; @@ -172,7 +382,7 @@ impl App { "{}{} ^P:pause ^R:reason ^K:kill | {} ", reason_indicator, proc_indicator, - self.status.model, + app.status.model, ); // Pad the middle to fill the status bar diff --git a/src/user/context.rs b/src/user/context.rs index 5a7a6ea..1085380 100644 --- a/src/user/context.rs +++ b/src/user/context.rs @@ -116,7 +116,7 @@ impl ScreenView for ConsciousScreen { fn label(&self) -> &'static str { "conscious" } fn tick(&mut self, frame: &mut Frame, area: Rect, - key: Option, app: &App) -> Option { + key: Option, app: &mut App) -> Option { // Handle keys if let Some(key) = key { let context_state = self.read_context_state(app); diff --git a/src/user/event_loop.rs b/src/user/event_loop.rs index ad0c8db..4a7253d 100644 --- a/src/user/event_loop.rs +++ b/src/user/event_loop.rs @@ -14,7 +14,7 @@ use tokio::sync::Mutex; use crate::agent::Agent; use crate::agent::api::ApiClient; use crate::mind::MindCommand; -use crate::user::{self as tui, HotkeyAction}; +use crate::user::{self as tui, HotkeyAction, ScreenView}; use crate::user::ui_channel::{self, UiMessage}; // ── Slash commands ───────────────────────────────────────────── @@ -381,14 +381,16 @@ pub async fn run( } let notify_rx = crate::thalamus::channels::subscribe_all(); - // Overlay screens (F2-F5). Index 0 = no overlay (interact). + // InteractScreen held separately for UiMessage routing + let mut interact = crate::user::chat::InteractScreen::new(); + // Overlay screens: F2=conscious, F3=subconscious, F4=unconscious, F5=thalamus let mut screens: Vec> = vec![ Box::new(crate::user::context::ConsciousScreen::new()), Box::new(crate::user::subconscious::SubconsciousScreen::new()), Box::new(crate::user::unconscious::UnconsciousScreen::new()), Box::new(crate::user::thalamus::ThalamusScreen::new()), ]; - let mut active_screen: usize = 0; // 0 = interact, 1-4 = overlay index + 1 + let mut active_screen: usize = 0; // 0 = interact, 1-4 = overlay let mut terminal = tui::init_terminal()?; let mut reader = EventStream::new(); @@ -403,9 +405,11 @@ pub async fn run( let _ = ui_tx.send(UiMessage::Info("consciousness v0.3 (tui)".into())); - // Initial render — don't wait for Mind to init - app.drain_messages(&mut ui_rx); - terminal.draw(|f| app.draw(f))?; + // Initial render + terminal.draw(|f| { + let area = f.area(); + interact.tick(f, area, None, &mut app); + })?; // Replay conversation after Mind init completes (non-blocking check) let mut startup_done = false; @@ -423,8 +427,8 @@ pub async fn run( // F-keys switch screens if let ratatui::crossterm::event::KeyCode::F(n) = key.code { active_screen = match n { - 1 => 0, - n @ 2..=5 => n as usize - 1, + 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 @@ -438,25 +442,18 @@ pub async fn run( continue; } - // App handles global keys (Ctrl combos) + interact keys - if active_screen == 0 { - app.handle_key(key); - } else { - // Global keys only - app.handle_key(key); - // Screen gets the key on next render tick - } + // Global keys (Ctrl combos) + app.handle_global_key(key); // Store pending key for active overlay screen pending_key = Some(key); dirty = true; } - Some(Ok(Event::Mouse(mouse))) => { - app.handle_mouse(mouse); + Some(Ok(Event::Mouse(_mouse))) => { + // TODO: route to active screen dirty = true; } - Some(Ok(Event::Resize(w, h))) => { - app.handle_resize(w, h); + Some(Ok(Event::Resize(_w, _h))) => { terminal.clear()?; dirty = true; } @@ -506,7 +503,7 @@ pub async fn run( } Some(msg) = ui_rx.recv() => { - app.handle_ui_message(msg); + interact.handle_ui_message(&msg, &mut app); dirty = true; } } @@ -540,7 +537,9 @@ pub async fn run( } } - if app.drain_messages(&mut ui_rx) { + // Drain UiMessages to interact screen + while let Ok(msg) = ui_rx.try_recv() { + interact.handle_ui_message(&msg, &mut app); dirty = true; } @@ -548,12 +547,15 @@ pub async fn run( let key = pending_key.take(); let mut screen_action = None; if active_screen == 0 { - terminal.draw(|f| app.draw(f))?; + terminal.draw(|f| { + let area = f.area(); + screen_action = interact.tick(f, area, key, &mut app); + })?; } else { let screen = &mut screens[active_screen - 1]; terminal.draw(|f| { let area = f.area(); - screen_action = screen.tick(f, area, key, &app); + screen_action = screen.tick(f, area, key, &mut app); })?; } if let Some(action) = screen_action { diff --git a/src/user/mod.rs b/src/user/mod.rs index dcde774..c482f2d 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -15,20 +15,18 @@ pub mod thalamus; // --- TUI infrastructure (moved from tui/mod.rs) --- use ratatui::crossterm::{ - event::{EnableMouseCapture, DisableMouseCapture, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind, MouseButton}, + event::{EnableMouseCapture, DisableMouseCapture, KeyCode, KeyEvent, KeyModifiers}, terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}, ExecutableCommand, }; use ratatui::{ backend::CrosstermBackend, - layout::Rect, style::{Color, Style}, text::{Line, Span}, - Frame, Terminal, }; use std::io; -use crate::user::ui_channel::{ContextInfo, SharedContextState, StatusInfo, UiMessage}; +use crate::user::ui_channel::{ContextInfo, SharedContextState, StatusInfo}; /// Build the screen legend from the screen table. pub(crate) fn screen_legend() -> String { @@ -246,7 +244,7 @@ pub enum ScreenAction { /// A screen that can draw itself and handle input. pub(crate) trait ScreenView: Send { fn tick(&mut self, frame: &mut ratatui::Frame, area: ratatui::layout::Rect, - key: Option, app: &App) -> Option; + key: Option, app: &mut App) -> Option; fn label(&self) -> &'static str; } @@ -276,29 +274,17 @@ pub(crate) struct ChannelStatus { } pub struct App { - pub(crate) autonomous: PaneState, - pub(crate) conversation: PaneState, - pub(crate) tools: PaneState, pub(crate) status: StatusInfo, pub(crate) activity: String, - pub(crate) turn_started: Option, - pub(crate) call_started: Option, - pub(crate) call_timeout_secs: u64, - pub(crate) needs_assistant_marker: bool, pub running_processes: u32, pub reasoning_effort: String, pub temperature: f32, pub top_p: f32, pub top_k: u32, pub(crate) active_tools: crate::user::ui_channel::SharedActiveTools, - pub(crate) active_pane: ActivePane, - pub textarea: tui_textarea::TextArea<'static>, - input_history: Vec, - history_index: Option, pub should_quit: bool, pub submitted: Vec, pub hotkey_actions: Vec, - pub(crate) pane_areas: [Rect; 3], pub(crate) context_info: Option, pub(crate) shared_context: SharedContextState, pub(crate) agent_state: Vec, @@ -309,218 +295,39 @@ pub struct App { impl App { pub fn new(model: String, shared_context: SharedContextState, active_tools: crate::user::ui_channel::SharedActiveTools) -> Self { Self { - autonomous: PaneState::new(true), - conversation: PaneState::new(true), - tools: PaneState::new(false), status: StatusInfo { dmn_state: "resting".into(), dmn_turns: 0, dmn_max_turns: 20, prompt_tokens: 0, completion_tokens: 0, model, turn_tools: 0, context_budget: String::new(), }, activity: String::new(), - turn_started: None, call_started: None, call_timeout_secs: 60, - needs_assistant_marker: false, running_processes: 0, + running_processes: 0, reasoning_effort: "none".to_string(), temperature: 0.6, top_p: 0.95, top_k: 20, - active_tools, active_pane: ActivePane::Conversation, - textarea: new_textarea(vec![String::new()]), - input_history: Vec::new(), history_index: None, + active_tools, should_quit: false, submitted: Vec::new(), hotkey_actions: Vec::new(), - pane_areas: [Rect::default(); 3], context_info: None, shared_context, agent_state: Vec::new(), channel_status: Vec::new(), idle_info: None, } } - pub fn drain_messages(&mut self, rx: &mut crate::user::ui_channel::UiReceiver) -> bool { - let mut any = false; - while let Ok(msg) = rx.try_recv() { - self.handle_ui_message(msg); - any = true; - } - any - } + pub fn handle_global_key_old(&mut self, _key: KeyEvent) -> bool { false } // placeholder - pub fn handle_ui_message(&mut self, msg: UiMessage) { - use crate::user::ui_channel::StreamTarget; - match msg { - UiMessage::TextDelta(text, target) => match target { - StreamTarget::Conversation => { - if self.needs_assistant_marker { - self.conversation.pending_marker = Marker::Assistant; - self.needs_assistant_marker = false; - } - self.conversation.current_color = Color::Reset; - self.conversation.append_text(&text); - } - StreamTarget::Autonomous => { - self.autonomous.current_color = Color::Reset; - self.autonomous.append_text(&text); - } - }, - UiMessage::UserInput(text) => { - self.conversation.push_line_with_marker(text, Color::Cyan, Marker::User); - self.turn_started = Some(std::time::Instant::now()); - self.needs_assistant_marker = true; - self.status.turn_tools = 0; - } - UiMessage::ToolCall { name, args_summary } => { - self.status.turn_tools += 1; - let line = if args_summary.is_empty() { format!("[{}]", name) } - else { format!("[{}] {}", name, args_summary) }; - self.tools.push_line(line, Color::Yellow); - } - UiMessage::ToolResult { name: _, result } => { - for line in result.lines() { - self.tools.push_line(format!(" {}", line), Color::DarkGray); - } - self.tools.push_line(String::new(), Color::Reset); - } - UiMessage::DmnAnnotation(text) => { - self.autonomous.push_line(text, Color::Yellow); - self.turn_started = Some(std::time::Instant::now()); - self.needs_assistant_marker = true; - self.status.turn_tools = 0; - } - UiMessage::StatusUpdate(info) => { - if !info.dmn_state.is_empty() { - self.status.dmn_state = info.dmn_state; - self.status.dmn_turns = info.dmn_turns; - self.status.dmn_max_turns = info.dmn_max_turns; - } - if info.prompt_tokens > 0 { self.status.prompt_tokens = info.prompt_tokens; } - if !info.model.is_empty() { self.status.model = info.model; } - if !info.context_budget.is_empty() { self.status.context_budget = info.context_budget; } - } - UiMessage::Activity(text) => { - if text.is_empty() { - self.call_started = None; - } else if self.activity.is_empty() || self.call_started.is_none() { - self.call_started = Some(std::time::Instant::now()); - self.call_timeout_secs = crate::config::get().api_stream_timeout_secs; - } - self.activity = text; - } - UiMessage::Reasoning(text) => { - self.autonomous.current_color = Color::DarkGray; - self.autonomous.append_text(&text); - } - UiMessage::ToolStarted { .. } | UiMessage::ToolFinished { .. } => {} - UiMessage::Debug(text) => { - self.tools.push_line(format!("[debug] {}", text), Color::DarkGray); - } - UiMessage::Info(text) => { - self.conversation.push_line(text, Color::Cyan); - } - UiMessage::ContextInfoUpdate(info) => { self.context_info = Some(info); } - UiMessage::AgentUpdate(agents) => { self.agent_state = agents; } - } - } - - /// Handle global keys only (Ctrl combos, F-keys). Screen-specific - /// keys are handled by the active ScreenView's tick method. - pub fn handle_key(&mut self, key: KeyEvent) { + /// 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; } - KeyCode::Char('r') => { self.hotkey_actions.push(HotkeyAction::CycleReasoning); return; } - KeyCode::Char('k') => { self.hotkey_actions.push(HotkeyAction::KillProcess); return; } - KeyCode::Char('p') => { self.hotkey_actions.push(HotkeyAction::CycleAutonomy); return; } + 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; } _ => {} } } - - match key.code { - KeyCode::Esc => { self.hotkey_actions.push(HotkeyAction::Interrupt); } - KeyCode::Enter if !key.modifiers.contains(KeyModifiers::ALT) && !key.modifiers.contains(KeyModifiers::SHIFT) => { - let input: String = self.textarea.lines().join("\n"); - if !input.is_empty() { - if self.input_history.last().map_or(true, |h| h != &input) { self.input_history.push(input.clone()); } - self.history_index = None; - self.submitted.push(input); - self.textarea = new_textarea(vec![String::new()]); - } - } - KeyCode::Up if key.modifiers.contains(KeyModifiers::CONTROL) => self.scroll_active_up(3), - KeyCode::Down if key.modifiers.contains(KeyModifiers::CONTROL) => self.scroll_active_down(3), - KeyCode::Up => { - if !self.input_history.is_empty() { - let idx = match self.history_index { None => self.input_history.len() - 1, Some(i) => i.saturating_sub(1) }; - self.history_index = Some(idx); - let mut ta = new_textarea(self.input_history[idx].lines().map(String::from).collect()); - ta.move_cursor(tui_textarea::CursorMove::End); - self.textarea = ta; - } - } - KeyCode::Down => { - if let Some(idx) = self.history_index { - if idx + 1 < self.input_history.len() { - self.history_index = Some(idx + 1); - let mut ta = new_textarea(self.input_history[idx + 1].lines().map(String::from).collect()); - ta.move_cursor(tui_textarea::CursorMove::End); - self.textarea = ta; - } else { - self.history_index = None; - self.textarea = new_textarea(vec![String::new()]); - } - } - } - KeyCode::PageUp => self.scroll_active_up(10), - KeyCode::PageDown => self.scroll_active_down(10), - KeyCode::Tab => { - self.active_pane = match self.active_pane { - ActivePane::Autonomous => ActivePane::Tools, - ActivePane::Tools => ActivePane::Conversation, - ActivePane::Conversation => ActivePane::Autonomous, - }; - } - _ => { self.textarea.input(key); } - } - } - - fn scroll_active_up(&mut self, n: u16) { - match self.active_pane { - ActivePane::Autonomous => self.autonomous.scroll_up(n), - ActivePane::Conversation => self.conversation.scroll_up(n), - ActivePane::Tools => self.tools.scroll_up(n), - } - } - - fn scroll_active_down(&mut self, n: u16) { - match self.active_pane { - ActivePane::Autonomous => self.autonomous.scroll_down(n), - ActivePane::Conversation => self.conversation.scroll_down(n), - ActivePane::Tools => self.tools.scroll_down(n), - } - } - - pub fn handle_resize(&mut self, _width: u16, _height: u16) {} - - pub fn handle_mouse(&mut self, mouse: MouseEvent) { - match mouse.kind { - MouseEventKind::ScrollUp => self.scroll_active_up(3), - MouseEventKind::ScrollDown => self.scroll_active_down(3), - MouseEventKind::Down(MouseButton::Left) => { - let (x, y) = (mouse.column, mouse.row); - for (i, area) in self.pane_areas.iter().enumerate() { - if x >= area.x && x < area.x + area.width && y >= area.y && y < area.y + area.height { - self.active_pane = match i { 0 => ActivePane::Autonomous, 1 => ActivePane::Conversation, _ => ActivePane::Tools }; - break; - } - } - } - _ => {} - } - } - - /// Draw the interact (F1) screen. Overlay screens are drawn - /// by the event loop via ScreenView::tick. - pub fn draw(&mut self, frame: &mut Frame) { - let size = frame.area(); - self.draw_main(frame, size); + false } pub fn set_channel_status(&mut self, channels: Vec<(String, bool, u32)>) { @@ -539,16 +346,16 @@ impl App { } -pub fn init_terminal() -> io::Result>> { +pub fn init_terminal() -> io::Result>> { terminal::enable_raw_mode()?; let mut stdout = io::stdout(); stdout.execute(EnterAlternateScreen)?; stdout.execute(EnableMouseCapture)?; let backend = CrosstermBackend::new(stdout); - Terminal::new(backend) + ratatui::Terminal::new(backend) } -pub fn restore_terminal(terminal: &mut Terminal>) -> io::Result<()> { +pub fn restore_terminal(terminal: &mut ratatui::Terminal>) -> io::Result<()> { terminal::disable_raw_mode()?; terminal.backend_mut().execute(DisableMouseCapture)?; terminal.backend_mut().execute(LeaveAlternateScreen)?; diff --git a/src/user/subconscious.rs b/src/user/subconscious.rs index 7e53d59..1d68a28 100644 --- a/src/user/subconscious.rs +++ b/src/user/subconscious.rs @@ -27,7 +27,7 @@ impl ScreenView for SubconsciousScreen { fn label(&self) -> &'static str { "subconscious" } fn tick(&mut self, frame: &mut Frame, area: Rect, - key: Option, app: &App) -> Option { + key: Option, app: &mut App) -> Option { // Handle keys if let Some(key) = key { match key.code { diff --git a/src/user/thalamus.rs b/src/user/thalamus.rs index 82fb71e..a37b46b 100644 --- a/src/user/thalamus.rs +++ b/src/user/thalamus.rs @@ -26,7 +26,7 @@ impl ScreenView for ThalamusScreen { fn label(&self) -> &'static str { "thalamus" } fn tick(&mut self, frame: &mut Frame, area: Rect, - key: Option, app: &App) -> Option { + key: Option, app: &mut App) -> Option { // Handle keys if let Some(key) = key { match key.code { diff --git a/src/user/unconscious.rs b/src/user/unconscious.rs index 8e2a9e5..e262d46 100644 --- a/src/user/unconscious.rs +++ b/src/user/unconscious.rs @@ -38,7 +38,7 @@ impl ScreenView for UnconsciousScreen { fn label(&self) -> &'static str { "unconscious" } fn tick(&mut self, frame: &mut Frame, area: Rect, - key: Option, _app: &App) -> Option { + key: Option, _app: &mut App) -> Option { if let Some(key) = key { match key.code { KeyCode::PageUp => { self.scroll = self.scroll.saturating_sub(20); }