Fix chat display: restore incremental sync with change detection

sync_from_agent now detects changed entries by comparing token counts
(cheap proxy for content changes during streaming). Changed entries
get popped and re-pushed. Extracted push_routed/pop_routed helpers.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-08 16:55:18 -04:00
parent 31e813f57d
commit 5f5a8a807c

View file

@ -456,6 +456,43 @@ impl InteractScreen {
} }
} }
fn push_routed(&mut self, node: &AstNode) {
for (target, text, marker) in Self::route_node(node) {
match target {
PaneTarget::Conversation => {
self.conversation.current_color = Color::Cyan;
self.conversation.append_text(&text);
self.conversation.pending_marker = marker;
self.conversation.flush_pending();
},
PaneTarget::ConversationAssistant => {
self.conversation.current_color = Color::Reset;
self.conversation.append_text(&text);
self.conversation.pending_marker = marker;
self.conversation.flush_pending();
},
PaneTarget::Tools =>
self.tools.push_line(text, Color::Yellow),
PaneTarget::ToolResult => {
for line in text.lines().take(20) {
self.tools.push_line(format!(" {}", line), Color::DarkGray);
}
}
}
}
}
fn pop_routed(&mut self, node: &AstNode) {
for (target, _, _) in Self::route_node(node) {
match target {
PaneTarget::Conversation | PaneTarget::ConversationAssistant
=> self.conversation.pop_line(),
PaneTarget::Tools | PaneTarget::ToolResult
=> self.tools.pop_line(),
}
}
}
fn sync_from_agent(&mut self) { fn sync_from_agent(&mut self) {
for _ in 0..self.pending_display_count { for _ in 0..self.pending_display_count {
self.conversation.pop_line(); self.conversation.pop_line();
@ -476,38 +513,37 @@ impl InteractScreen {
(generation, ctx.conversation().to_vec()) (generation, ctx.conversation().to_vec())
}; };
if generation != self.last_generation || entries.len() < self.last_entries.len() { // Full reset on generation change
if generation != self.last_generation {
self.conversation = PaneState::new(true); self.conversation = PaneState::new(true);
self.autonomous = PaneState::new(true); self.autonomous = PaneState::new(true);
self.tools = PaneState::new(false); self.tools = PaneState::new(false);
self.last_entries.clear(); self.last_entries.clear();
} }
let start = self.last_entries.len(); // Detect changed entries (streaming updates mutate the last entry)
for node in entries.iter().skip(start) { // Walk backwards from the end, pop any that differ
for (target, text, marker) in Self::route_node(node) { let mut pop_from = self.last_entries.len();
match target { for i in (0..self.last_entries.len()).rev() {
PaneTarget::Conversation => { if i >= entries.len() {
self.conversation.current_color = Color::Cyan; pop_from = i;
self.conversation.append_text(&text); continue;
self.conversation.pending_marker = marker;
self.conversation.flush_pending();
},
PaneTarget::ConversationAssistant => {
self.conversation.current_color = Color::Reset;
self.conversation.append_text(&text);
self.conversation.pending_marker = marker;
self.conversation.flush_pending();
},
PaneTarget::Tools =>
self.tools.push_line(text, Color::Yellow),
PaneTarget::ToolResult => {
for line in text.lines().take(20) {
self.tools.push_line(format!(" {}", line), Color::DarkGray);
}
}
}
} }
// Compare token count as a cheap change detector
if self.last_entries[i].tokens() != entries[i].tokens() {
pop_from = i;
} else {
break; // entries before this haven't changed
}
}
while self.last_entries.len() > pop_from {
let popped = self.last_entries.pop().unwrap();
self.pop_routed(&popped);
}
// Push new/changed entries
for node in entries.iter().skip(self.last_entries.len()) {
self.push_routed(node);
self.last_entries.push(node.clone()); self.last_entries.push(node.clone());
} }