tui: overlay screens via F-keys (F1=context, F2=agents)
Replaced debug_visible bool with an Overlay enum. F1 shows the context/debug screen (Ctrl+D still works as alias), F2 shows the agents screen (placeholder for now — will show surface, observe, reflect, journal status). Esc closes any overlay. Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
c5efc6e650
commit
baf208281d
2 changed files with 69 additions and 24 deletions
|
|
@ -9,6 +9,8 @@
|
||||||
// Uses ratatui + crossterm. The App struct holds all TUI state and
|
// Uses ratatui + crossterm. The App struct holds all TUI state and
|
||||||
// handles rendering. Input is processed from crossterm key events.
|
// handles rendering. Input is processed from crossterm key events.
|
||||||
|
|
||||||
|
const SCREEN_LEGEND: &str = " F1=main F2=agents F10=context ";
|
||||||
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{EnableMouseCapture, DisableMouseCapture, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind, MouseButton},
|
event::{EnableMouseCapture, DisableMouseCapture, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind, MouseButton},
|
||||||
terminal::{self, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{self, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
|
|
@ -328,8 +330,8 @@ pub struct App {
|
||||||
pub hotkey_actions: Vec<HotkeyAction>,
|
pub hotkey_actions: Vec<HotkeyAction>,
|
||||||
/// Pane areas from last draw (for mouse click → pane selection).
|
/// Pane areas from last draw (for mouse click → pane selection).
|
||||||
pane_areas: [Rect; 3], // [autonomous, conversation, tools]
|
pane_areas: [Rect; 3], // [autonomous, conversation, tools]
|
||||||
/// Debug screen visible (Ctrl+D toggle).
|
/// Active overlay screen (F1=context, F2=agents, Ctrl+D=context).
|
||||||
debug_visible: bool,
|
overlay: Option<Overlay>,
|
||||||
/// Debug screen scroll offset.
|
/// Debug screen scroll offset.
|
||||||
debug_scroll: u16,
|
debug_scroll: u16,
|
||||||
/// Index of selected context section in debug view (for expand/collapse).
|
/// Index of selected context section in debug view (for expand/collapse).
|
||||||
|
|
@ -342,6 +344,15 @@ pub struct App {
|
||||||
shared_context: SharedContextState,
|
shared_context: SharedContextState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Overlay screens toggled by F-keys.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
enum Overlay {
|
||||||
|
/// F1 / Ctrl+D — context window, model info, budget
|
||||||
|
Context,
|
||||||
|
/// F2 — subconscious agent status
|
||||||
|
Agents,
|
||||||
|
}
|
||||||
|
|
||||||
/// Actions triggered by hotkeys, consumed by the main loop.
|
/// Actions triggered by hotkeys, consumed by the main loop.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum HotkeyAction {
|
pub enum HotkeyAction {
|
||||||
|
|
@ -385,7 +396,7 @@ impl App {
|
||||||
submitted: Vec::new(),
|
submitted: Vec::new(),
|
||||||
hotkey_actions: Vec::new(),
|
hotkey_actions: Vec::new(),
|
||||||
pane_areas: [Rect::default(); 3],
|
pane_areas: [Rect::default(); 3],
|
||||||
debug_visible: false,
|
overlay: None,
|
||||||
debug_scroll: 0,
|
debug_scroll: 0,
|
||||||
debug_selected: None,
|
debug_selected: None,
|
||||||
debug_expanded: std::collections::HashSet::new(),
|
debug_expanded: std::collections::HashSet::new(),
|
||||||
|
|
@ -510,8 +521,8 @@ impl App {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
KeyCode::Char('d') => {
|
KeyCode::Char('d') => {
|
||||||
self.debug_visible = !self.debug_visible;
|
// Legacy alias for F10
|
||||||
self.debug_scroll = 0;
|
self.set_overlay(Overlay::Context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
KeyCode::Char('p') => {
|
KeyCode::Char('p') => {
|
||||||
|
|
@ -522,13 +533,15 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug screen captures scroll keys and Esc
|
// Overlay screen captures scroll keys and Esc
|
||||||
if self.debug_visible {
|
if self.overlay.is_some() {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Esc => {
|
KeyCode::F(1) => {
|
||||||
self.debug_visible = false;
|
self.overlay = None;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
KeyCode::F(10) => { self.set_overlay(Overlay::Context); return; }
|
||||||
|
KeyCode::F(2) => { self.set_overlay(Overlay::Agents); return; }
|
||||||
KeyCode::Up => {
|
KeyCode::Up => {
|
||||||
let cs = self.read_context_state();
|
let cs = self.read_context_state();
|
||||||
let n = self.debug_item_count(&cs);
|
let n = self.debug_item_count(&cs);
|
||||||
|
|
@ -574,6 +587,8 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
match key.code {
|
match key.code {
|
||||||
|
KeyCode::F(10) => { self.set_overlay(Overlay::Context); return; }
|
||||||
|
KeyCode::F(2) => { self.set_overlay(Overlay::Agents); return; }
|
||||||
KeyCode::Esc => {
|
KeyCode::Esc => {
|
||||||
self.hotkey_actions.push(HotkeyAction::Interrupt);
|
self.hotkey_actions.push(HotkeyAction::Interrupt);
|
||||||
}
|
}
|
||||||
|
|
@ -695,9 +710,10 @@ impl App {
|
||||||
pub fn draw(&mut self, frame: &mut Frame) {
|
pub fn draw(&mut self, frame: &mut Frame) {
|
||||||
let size = frame.area();
|
let size = frame.area();
|
||||||
|
|
||||||
if self.debug_visible {
|
match self.overlay {
|
||||||
self.draw_debug(frame, size);
|
Some(Overlay::Context) => { self.draw_debug(frame, size); return; }
|
||||||
return;
|
Some(Overlay::Agents) => { self.draw_agents(frame, size); return; }
|
||||||
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main layout: content area + active tools overlay + status bar
|
// Main layout: content area + active tools overlay + status bar
|
||||||
|
|
@ -744,11 +760,12 @@ impl App {
|
||||||
|
|
||||||
// Draw autonomous pane
|
// Draw autonomous pane
|
||||||
let auto_active = self.active_pane == ActivePane::Autonomous;
|
let auto_active = self.active_pane == ActivePane::Autonomous;
|
||||||
draw_pane(frame, auto_area, "autonomous", &mut self.autonomous, auto_active);
|
draw_pane(frame, auto_area, "autonomous", &mut self.autonomous, auto_active,
|
||||||
|
Some(SCREEN_LEGEND));
|
||||||
|
|
||||||
// Draw tools pane
|
// Draw tools pane
|
||||||
let tools_active = self.active_pane == ActivePane::Tools;
|
let tools_active = self.active_pane == ActivePane::Tools;
|
||||||
draw_pane(frame, right_col, "tools", &mut self.tools, tools_active);
|
draw_pane(frame, right_col, "tools", &mut self.tools, tools_active, None);
|
||||||
|
|
||||||
// Draw conversation pane (with input line)
|
// Draw conversation pane (with input line)
|
||||||
let conv_active = self.active_pane == ActivePane::Conversation;
|
let conv_active = self.active_pane == ActivePane::Conversation;
|
||||||
|
|
@ -849,7 +866,7 @@ impl App {
|
||||||
String::new()
|
String::new()
|
||||||
};
|
};
|
||||||
let right_legend = format!(
|
let right_legend = format!(
|
||||||
"{}{} ^P:pause ^D:debug ^R:reason ^K:kill | {} ",
|
"{}{} ^P:pause ^R:reason ^K:kill | {} ",
|
||||||
reason_indicator,
|
reason_indicator,
|
||||||
proc_indicator,
|
proc_indicator,
|
||||||
self.status.model,
|
self.status.model,
|
||||||
|
|
@ -961,15 +978,35 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_debug(&self, frame: &mut Frame, size: Rect) {
|
fn set_overlay(&mut self, screen: Overlay) {
|
||||||
|
self.overlay = Some(screen);
|
||||||
|
self.debug_scroll = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_agents(&self, frame: &mut Frame, size: Rect) {
|
||||||
let mut lines: Vec<Line> = Vec::new();
|
let mut lines: Vec<Line> = Vec::new();
|
||||||
let section = Style::default().fg(Color::Yellow);
|
let section = Style::default().fg(Color::Yellow);
|
||||||
|
|
||||||
lines.push(Line::styled(
|
|
||||||
" Debug (Ctrl+D or Esc to close, arrows/PgUp/PgDn to scroll)",
|
|
||||||
Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD),
|
|
||||||
));
|
|
||||||
lines.push(Line::raw(""));
|
lines.push(Line::raw(""));
|
||||||
|
lines.push(Line::styled("── Subconscious Agents ──", section));
|
||||||
|
lines.push(Line::raw(""));
|
||||||
|
lines.push(Line::raw(" (not yet wired — will show surface, observe, reflect, journal status)"));
|
||||||
|
|
||||||
|
let block = Block::default()
|
||||||
|
.title_top(Line::from(SCREEN_LEGEND).left_aligned())
|
||||||
|
.title_top(Line::from(" agents ").right_aligned())
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_style(Style::default().fg(Color::Cyan));
|
||||||
|
|
||||||
|
let para = Paragraph::new(lines)
|
||||||
|
.block(block)
|
||||||
|
.scroll((self.debug_scroll, 0));
|
||||||
|
frame.render_widget(para, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_debug(&self, frame: &mut Frame, size: Rect) {
|
||||||
|
let mut lines: Vec<Line> = Vec::new();
|
||||||
|
let section = Style::default().fg(Color::Yellow);
|
||||||
|
|
||||||
// Model
|
// Model
|
||||||
lines.push(Line::styled("── Model ──", section));
|
lines.push(Line::styled("── Model ──", section));
|
||||||
|
|
@ -1024,7 +1061,8 @@ impl App {
|
||||||
lines.push(Line::raw(format!(" Active tools: {}", self.active_tools.len())));
|
lines.push(Line::raw(format!(" Active tools: {}", self.active_tools.len())));
|
||||||
|
|
||||||
let block = Block::default()
|
let block = Block::default()
|
||||||
.title(" Debug ")
|
.title_top(Line::from(SCREEN_LEGEND).left_aligned())
|
||||||
|
.title_top(Line::from(" context ").right_aligned())
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_style(Style::default().fg(Color::Cyan));
|
.border_style(Style::default().fg(Color::Cyan));
|
||||||
|
|
||||||
|
|
@ -1142,6 +1180,7 @@ fn draw_pane(
|
||||||
title: &str,
|
title: &str,
|
||||||
pane: &mut PaneState,
|
pane: &mut PaneState,
|
||||||
is_active: bool,
|
is_active: bool,
|
||||||
|
left_title: Option<&str>,
|
||||||
) {
|
) {
|
||||||
let inner_height = area.height.saturating_sub(2);
|
let inner_height = area.height.saturating_sub(2);
|
||||||
|
|
||||||
|
|
@ -1151,10 +1190,16 @@ fn draw_pane(
|
||||||
Style::default().fg(Color::DarkGray)
|
Style::default().fg(Color::DarkGray)
|
||||||
};
|
};
|
||||||
|
|
||||||
let block = Block::default()
|
let mut block = Block::default()
|
||||||
.title(format!(" {} ", title))
|
|
||||||
.borders(Borders::ALL)
|
.borders(Borders::ALL)
|
||||||
.border_style(border_style);
|
.border_style(border_style);
|
||||||
|
if let Some(left) = left_title {
|
||||||
|
block = block
|
||||||
|
.title_top(Line::from(left).left_aligned())
|
||||||
|
.title_top(Line::from(format!(" {} ", title)).right_aligned());
|
||||||
|
} else {
|
||||||
|
block = block.title(format!(" {} ", title));
|
||||||
|
}
|
||||||
|
|
||||||
let lines = pane.all_lines();
|
let lines = pane.all_lines();
|
||||||
let paragraph = Paragraph::new(lines)
|
let paragraph = Paragraph::new(lines)
|
||||||
|
|
|
||||||
|
|
@ -628,7 +628,7 @@ impl Session {
|
||||||
"Keys: Tab=pane ^Up/Down=scroll PgUp/PgDn=scroll Mouse=click/scroll".into(),
|
"Keys: Tab=pane ^Up/Down=scroll PgUp/PgDn=scroll Mouse=click/scroll".into(),
|
||||||
));
|
));
|
||||||
let _ = self.ui_tx.send(UiMessage::Info(
|
let _ = self.ui_tx.send(UiMessage::Info(
|
||||||
" Alt+Enter=newline Esc=interrupt ^P=pause ^R=reasoning ^K=kill ^D=debug".into(),
|
" Alt+Enter=newline Esc=interrupt ^P=pause ^R=reasoning ^K=kill F10=context F2=agents".into(),
|
||||||
));
|
));
|
||||||
let _ = self.ui_tx.send(UiMessage::Info(
|
let _ = self.ui_tx.send(UiMessage::Info(
|
||||||
" Shift+click for native text selection (copy/paste)".into(),
|
" Shift+click for native text selection (copy/paste)".into(),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue