fix: slim down token usage

This commit is contained in:
eric
2026-04-04 12:37:50 +02:00
parent 97f329c825
commit 1240ab946b
55 changed files with 6799 additions and 2333 deletions

View File

@@ -7,13 +7,14 @@ mod workspace_input;
#[cfg(test)]
mod tests;
use std::io::Write;
use std::path::PathBuf;
use std::sync::mpsc::{Receiver, Sender};
use std::time::{Duration, Instant};
use anyhow::Result;
use crossterm::{
event::{self, Event, KeyEvent},
event::{self, Event},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
@@ -21,14 +22,15 @@ use ratatui::{backend::CrosstermBackend, Terminal};
use crate::cli::DEFAULT_TASK_CONFIG_PATH;
use crate::model::{
group_session_entries, ControllerPhase, ControllerState, Plan, Screen, SessionEntry,
SessionSelection, StatusSnapshot, TaskConfig, UsageSnapshot,
ControllerState, Plan, Screen, SessionEntry, SessionSelection, StatusSnapshot, TaskConfig,
UsageSnapshot,
};
use crate::ui;
use crate::ui::{self, scroll::VerticalScrollState, SessionRenderRow, SessionView, SidebarView};
pub(crate) const USAGE_REFRESH_INTERVAL: Duration = Duration::from_secs(120);
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum AppEvent {
Session(SessionEntry),
Snapshot {
@@ -66,9 +68,15 @@ pub(crate) struct WorkspaceRuntime {
pub(crate) session_output_tokens: Option<u64>,
pub(crate) usage_snapshot: UsageSnapshot,
pub(crate) last_usage_refresh: Instant,
pub(crate) session_scroll: usize,
pub(crate) session_follow_output: bool,
pub(crate) session_viewport_lines: usize,
pub(crate) session_scrollbar: VerticalScrollState,
pub(crate) session_rows: Vec<SessionRenderRow>,
pub(crate) session_view: Option<SessionView>,
pub(crate) session_view_area: ratatui::layout::Rect,
pub(crate) sidebar_follow_output: bool,
pub(crate) sidebar_scrollbar: VerticalScrollState,
pub(crate) sidebar_view: Option<SidebarView>,
pub(crate) sidebar_view_area: ratatui::layout::Rect,
pub(crate) session_selection: Option<SessionSelection>,
pub(crate) session_drag_active: bool,
}
@@ -80,6 +88,7 @@ pub struct App {
pub create_input: String,
pub create_error: Option<String>,
pub default_task_path: PathBuf,
pub(crate) frame_tick: u64,
pub(crate) workspace: Option<WorkspaceRuntime>,
}
@@ -93,6 +102,7 @@ impl App {
create_input: String::new(),
create_error: None,
default_task_path: default_task_path.clone(),
frame_tick: 0,
workspace: None,
};
@@ -108,7 +118,11 @@ impl App {
pub fn run(&mut self) -> Result<()> {
enable_raw_mode()?;
let mut stdout = std::io::stdout();
execute!(stdout, EnterAlternateScreen, crossterm::event::EnableMouseCapture)?;
execute!(
stdout,
EnterAlternateScreen,
crossterm::event::EnableMouseCapture
)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
@@ -126,13 +140,6 @@ impl App {
result
}
pub fn workspace_groups(&self) -> Vec<crate::model::SessionGroup> {
self.workspace
.as_ref()
.map(|workspace| group_session_entries(&workspace.session_entries))
.unwrap_or_default()
}
pub fn workspace_status_snapshot(&self) -> Option<StatusSnapshot> {
let workspace = self.workspace.as_ref()?;
Some(StatusSnapshot {
@@ -163,6 +170,28 @@ impl App {
.and_then(|workspace| workspace.session_selection.as_ref())
}
pub(crate) fn workspace_session_rows(&self) -> Option<&[SessionRenderRow]> {
self.workspace
.as_ref()
.map(|workspace| workspace.session_rows.as_slice())
}
pub(crate) fn workspace_session_view(&self) -> Option<&SessionView> {
self.workspace
.as_ref()
.and_then(|workspace| workspace.session_view.as_ref())
}
pub(crate) fn workspace_sidebar_view(&self) -> Option<&SidebarView> {
self.workspace
.as_ref()
.and_then(|workspace| workspace.sidebar_view.as_ref())
}
pub(crate) fn heartbeat_frame(&self) -> u64 {
self.frame_tick
}
pub(crate) fn workspace_session_scroll(&self) -> usize {
let Some(workspace) = self.workspace.as_ref() else {
return 0;
@@ -170,18 +199,18 @@ impl App {
let max_scroll = self
.workspace_session_line_count()
.saturating_sub(workspace.session_viewport_lines);
.saturating_sub(workspace.session_scrollbar.viewport_lines);
if workspace.session_follow_output {
max_scroll
} else {
workspace.session_scroll.min(max_scroll)
workspace.session_scrollbar.position_lines.min(max_scroll)
}
}
pub(crate) fn workspace_session_line_count(&self) -> usize {
self.workspace
.as_ref()
.map(|workspace| Self::session_line_count_for_entries(&workspace.session_entries))
.map(|workspace| workspace.session_scrollbar.content_lines)
.unwrap_or_default()
}
@@ -192,24 +221,42 @@ impl App {
loop {
self.drain_workspace_events()?;
self.maybe_refresh_usage()?;
self.update_workspace_viewport(terminal.size()?.height as usize);
self.update_workspace_viewport(terminal.size()?.into());
self.tick_session_scroll_repeat();
self.frame_tick = self.frame_tick.wrapping_add(1);
terminal.backend_mut().write_all(b"\x1b[?2026h")?;
terminal.draw(|frame| ui::render(frame, self))?;
terminal.backend_mut().write_all(b"\x1b[?2026l")?;
terminal.backend_mut().flush()?;
if event::poll(Duration::from_millis(100))? {
match event::read()? {
Event::Key(key) => {
if self.handle_key(key)? {
break;
}
}
Event::Mouse(mouse) => self.handle_mouse(mouse, terminal.size()?.into())?,
Event::Resize(_, height) => self.update_workspace_viewport(height as usize),
_ => {}
}
if event::poll(Duration::from_millis(50))? && self.handle_pending_events(terminal)? {
break;
}
}
Ok(())
}
fn handle_pending_events(
&mut self,
terminal: &mut Terminal<CrosstermBackend<std::io::Stdout>>,
) -> Result<bool> {
loop {
match event::read()? {
Event::Key(key) => {
if self.handle_key(key)? {
return Ok(true);
}
}
Event::Mouse(mouse) => self.handle_mouse(mouse, terminal.size()?.into())?,
Event::Resize(_, _) => self.update_workspace_viewport(terminal.size()?.into()),
_ => {}
}
if !event::poll(Duration::ZERO)? {
return Ok(false);
}
}
}
}