user: ScreenView trait, overlay screens extracted from App
Convert F2-F5 screens to ScreenView trait with tick() method. Each screen owns its view state (scroll, selection, expanded). State persists across screen switches. - ThalamusScreen: owns sampling_selected, scroll - ConsciousScreen: owns scroll, selected, expanded - SubconsciousScreen: owns selected, log_view, scroll - UnconsciousScreen: owns scroll Removed from App: Screen enum, debug_scroll, debug_selected, debug_expanded, agent_selected, agent_log_view, sampling_selected, set_screen(), per-screen key handling, draw dispatch. App now only draws the interact (F1) screen. Overlay screens are drawn by the event loop via ScreenView::tick. F-key routing and screen instantiation to be wired in event_loop next. InteractScreen (state-driven, reading from agent entries) is the next step — will eliminate the input display race condition. Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
parent
7458fe655f
commit
927cddd864
8 changed files with 388 additions and 439 deletions
|
|
@ -1,7 +1,4 @@
|
|||
// thalamus_screen.rs — F5: presence, idle state, and channel status
|
||||
//
|
||||
// Shows idle state from the in-process thalamus (no subprocess spawn),
|
||||
// then channel daemon status from cached data.
|
||||
|
||||
use ratatui::{
|
||||
layout::Rect,
|
||||
|
|
@ -9,34 +6,70 @@ use ratatui::{
|
|||
text::{Line, Span},
|
||||
widgets::{Block, Borders, Paragraph, Wrap},
|
||||
Frame,
|
||||
crossterm::event::{KeyCode, KeyEvent},
|
||||
};
|
||||
|
||||
use super::{App, SCREEN_LEGEND};
|
||||
use super::{App, HotkeyAction, ScreenAction, ScreenView, screen_legend};
|
||||
|
||||
impl App {
|
||||
pub(crate) fn draw_thalamus(&self, frame: &mut Frame, size: Rect) {
|
||||
pub(crate) struct ThalamusScreen {
|
||||
sampling_selected: usize,
|
||||
scroll: u16,
|
||||
}
|
||||
|
||||
impl ThalamusScreen {
|
||||
pub fn new() -> Self {
|
||||
Self { sampling_selected: 0, scroll: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl ScreenView for ThalamusScreen {
|
||||
fn label(&self) -> &'static str { "thalamus" }
|
||||
|
||||
fn tick(&mut self, frame: &mut Frame, area: Rect,
|
||||
key: Option<KeyEvent>, app: &App) -> Option<ScreenAction> {
|
||||
// Handle keys
|
||||
if let Some(key) = key {
|
||||
match key.code {
|
||||
KeyCode::Up => { self.sampling_selected = self.sampling_selected.saturating_sub(1); }
|
||||
KeyCode::Down => { self.sampling_selected = (self.sampling_selected + 1).min(2); }
|
||||
KeyCode::Right => {
|
||||
let delta = match self.sampling_selected {
|
||||
0 => 0.05, 1 => 0.05, 2 => 5.0, _ => 0.0,
|
||||
};
|
||||
return Some(ScreenAction::Hotkey(HotkeyAction::AdjustSampling(self.sampling_selected, delta)));
|
||||
}
|
||||
KeyCode::Left => {
|
||||
let delta = match self.sampling_selected {
|
||||
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
|
||||
let section = Style::default().fg(Color::Yellow);
|
||||
let dim = Style::default().fg(Color::DarkGray);
|
||||
let mut lines: Vec<Line> = Vec::new();
|
||||
|
||||
// Presence / idle state from in-process thalamus
|
||||
// Presence / idle state
|
||||
lines.push(Line::styled("── Presence ──", section));
|
||||
lines.push(Line::raw(""));
|
||||
|
||||
if let Some(ref idle) = self.idle_info {
|
||||
if let Some(ref idle) = app.idle_info {
|
||||
let presence = if idle.user_present {
|
||||
Span::styled("present", Style::default().fg(Color::Green))
|
||||
} else {
|
||||
Span::styled("away", Style::default().fg(Color::DarkGray))
|
||||
};
|
||||
lines.push(Line::from(vec![
|
||||
Span::raw(" User: "),
|
||||
presence,
|
||||
Span::raw(" User: "), presence,
|
||||
Span::raw(format!(" (last {:.0}s ago)", idle.since_activity)),
|
||||
]));
|
||||
lines.push(Line::raw(format!(" Activity: {:.1}%", idle.activity_ewma * 100.0)));
|
||||
lines.push(Line::raw(format!(" Idle state: {}", idle.block_reason)));
|
||||
|
||||
if idle.dreaming {
|
||||
lines.push(Line::styled(" ◆ dreaming", Style::default().fg(Color::Magenta)));
|
||||
}
|
||||
|
|
@ -48,13 +81,13 @@ impl App {
|
|||
}
|
||||
lines.push(Line::raw(""));
|
||||
|
||||
// Sampling parameters (↑/↓ select, ←/→ adjust)
|
||||
// Sampling parameters
|
||||
lines.push(Line::styled("── Sampling (←/→ adjust) ──", section));
|
||||
lines.push(Line::raw(""));
|
||||
let params = [
|
||||
format!("temperature: {:.2}", self.temperature),
|
||||
format!("top_p: {:.2}", self.top_p),
|
||||
format!("top_k: {}", self.top_k),
|
||||
format!("temperature: {:.2}", app.temperature),
|
||||
format!("top_p: {:.2}", app.top_p),
|
||||
format!("top_k: {}", app.top_k),
|
||||
];
|
||||
for (i, label) in params.iter().enumerate() {
|
||||
let prefix = if i == self.sampling_selected { "▸ " } else { " " };
|
||||
|
|
@ -67,41 +100,27 @@ impl App {
|
|||
}
|
||||
lines.push(Line::raw(""));
|
||||
|
||||
// Channel status from cached data
|
||||
// Channel status
|
||||
lines.push(Line::styled("── Channels ──", section));
|
||||
lines.push(Line::raw(""));
|
||||
|
||||
if self.channel_status.is_empty() {
|
||||
if app.channel_status.is_empty() {
|
||||
lines.push(Line::styled(" no channels configured", dim));
|
||||
} else {
|
||||
for ch in &self.channel_status {
|
||||
let (symbol, color) = if ch.connected {
|
||||
("●", Color::Green)
|
||||
} else {
|
||||
("○", Color::Red)
|
||||
};
|
||||
|
||||
let unread_str = if ch.unread > 0 {
|
||||
format!(" ({} unread)", ch.unread)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
for ch in &app.channel_status {
|
||||
let (symbol, color) = if ch.connected { ("●", Color::Green) } else { ("○", Color::Red) };
|
||||
let unread_str = if ch.unread > 0 { format!(" ({} unread)", ch.unread) } else { String::new() };
|
||||
lines.push(Line::from(vec![
|
||||
Span::raw(" "),
|
||||
Span::styled(symbol, Style::default().fg(color)),
|
||||
Span::raw(format!(" {:<24}", ch.name)),
|
||||
Span::styled(
|
||||
if ch.connected { "connected" } else { "disconnected" },
|
||||
Style::default().fg(color),
|
||||
),
|
||||
Span::styled(if ch.connected { "connected" } else { "disconnected" }, Style::default().fg(color)),
|
||||
Span::styled(unread_str, Style::default().fg(Color::Yellow)),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
let block = Block::default()
|
||||
.title_top(Line::from(SCREEN_LEGEND).left_aligned())
|
||||
.title_top(Line::from(screen_legend()).left_aligned())
|
||||
.title_top(Line::from(" thalamus ").right_aligned())
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default().fg(Color::Cyan));
|
||||
|
|
@ -109,8 +128,9 @@ impl App {
|
|||
let para = Paragraph::new(lines)
|
||||
.block(block)
|
||||
.wrap(Wrap { trim: false })
|
||||
.scroll((self.debug_scroll, 0));
|
||||
.scroll((self.scroll, 0));
|
||||
|
||||
frame.render_widget(para, size);
|
||||
frame.render_widget(para, area);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue