aboutsummaryrefslogtreecommitdiffstats
path: root/crates/atuin-ai/src/commands/inline.rs
diff options
context:
space:
mode:
authorMichelle Tilley <michelle@michelletilley.net>2026-04-23 20:31:53 -0700
committerGitHub <noreply@github.com>2026-04-23 20:31:53 -0700
commitf72fdf7565d18b044f035fa6aca9ae8dbba34fc6 (patch)
treed0c08204ed712f0f788cd262f56894b8a2af7d49 /crates/atuin-ai/src/commands/inline.rs
parentfeat: Add skill discovery, loading, and invocation (#3444) (diff)
downloadatuin-f72fdf7565d18b044f035fa6aca9ae8dbba34fc6.zip
perf: Reduce AI TUI rendering overhead for long conversations (#3447)
Fixes keystroke lag in Atuin AI that scales with conversation length. After extended use (many turns, lots of tool calls with output viewports), pressing a key had noticeable delay before the letter appeared. Three layers of optimization: - **Skip `sync_view_state` for `InputUpdated`** — every keystroke was cloning all events, tools, and archived data even though no FSM state changed. Uses `handle.update_tracked()` (eye_declare 0.5) to skip rebuilds when values haven't actually changed. - **Pre-compute turns and `has_command` on the driver thread** — the view function was rebuilding the full turn structure from raw events and scanning for `suggest_command` tool calls 3× per render frame. Now computed once per FSM state change and cached in ViewState. - **Commit-based element tree pruning** — turns that scroll into terminal scrollback are tracked via `on_commit` and filtered from the element tree, keeping rendering work proportional to visible content. Turn views are now direct children of the root VStack (not nested inside AtuinAi) so `detect_committed` can see them.
Diffstat (limited to 'crates/atuin-ai/src/commands/inline.rs')
-rw-r--r--crates/atuin-ai/src/commands/inline.rs48
1 files changed, 45 insertions, 3 deletions
diff --git a/crates/atuin-ai/src/commands/inline.rs b/crates/atuin-ai/src/commands/inline.rs
index 70f26c65..989b95c0 100644
--- a/crates/atuin-ai/src/commands/inline.rs
+++ b/crates/atuin-ai/src/commands/inline.rs
@@ -292,6 +292,17 @@ async fn run_inline_tui(
.bracketed_paste(true)
.with_context(tui_tx)
.extra_newlines_at_exit(1)
+ .on_commit(|committed, state| {
+ if let Some(key) = &committed.key
+ && let Some(id_str) = key.strip_prefix("turn-")
+ && let Ok(id) = id_str.parse::<usize>()
+ {
+ let new_count = id + 1;
+ if new_count > state.committed_turn_count {
+ state.committed_turn_count = new_count;
+ }
+ }
+ })
.build()?;
// ─── Driver loop ────────────────────────────────────────────
@@ -349,17 +360,48 @@ fn build_view_state(
skill_names.insert(skill.name.clone());
}
+ let tools = fsm.ctx.tools.clone();
+ let visible_events = fsm.ctx.events[safe_start..].to_vec();
+ let archived_events = fsm.ctx.archived_events.clone();
+
+ let mut archived_builder = crate::tui::view::turn::TurnBuilder::new(&tools);
+ for event in &archived_events {
+ archived_builder.add_event(event);
+ }
+ let archived_turns = archived_builder.build();
+ let archived_turn_count = archived_turns.len();
+
+ let mut visible_builder =
+ crate::tui::view::turn::TurnBuilder::new_starting_at(&tools, archived_turn_count);
+ for event in &visible_events {
+ visible_builder.add_event(event);
+ }
+ let visible_turns = visible_builder.build();
+
+ let mut turns = archived_turns;
+ turns.extend(visible_turns);
+
+ let has_command = visible_events.iter().any(|e| {
+ matches!(e, ConversationEvent::ToolCall { name, input, .. }
+ if name == "suggest_command"
+ && input.get("command").and_then(|v| v.as_str()).is_some())
+ });
+
ViewState {
agent_state: fsm.state.clone(),
- visible_events: fsm.ctx.events[safe_start..].to_vec(),
+ visible_events,
all_events: fsm.ctx.events.clone(),
session_id: fsm.ctx.session_id.clone(),
- tools: fsm.ctx.tools.clone(),
+ tools,
current_response: fsm.ctx.current_response.clone(),
is_resumed: fsm.ctx.is_resumed,
last_event_time: fsm.ctx.last_event_time,
in_git_project,
- archived_events: fsm.ctx.archived_events.clone(),
+ archived_events,
+ turns,
+ has_command,
+ committed_turn_count: 0,
+ archived_turn_count,
is_input_blank: true,
slash_command_input: None,
slash_command_search_results: Vec::new(),