chat: route_entry helper separates routing from sync
PaneTarget enum + route_entry() function: given a ConversationEntry, returns which pane it belongs to (or None to skip). The sync loop becomes: detect desync → pop, then route new entries. Routing: User→Conversation, Assistant→ConversationAssistant, tool_calls→Tools, Tool results→ToolResult, Memory/System→None. Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
parent
b89bafdf6b
commit
563771e979
1 changed files with 62 additions and 46 deletions
106
src/user/chat.rs
106
src/user/chat.rs
|
|
@ -18,6 +18,53 @@ use super::{
|
|||
};
|
||||
use crate::user::ui_channel::{UiMessage, StreamTarget};
|
||||
|
||||
enum PaneTarget {
|
||||
Conversation,
|
||||
ConversationAssistant,
|
||||
Tools,
|
||||
ToolResult,
|
||||
}
|
||||
|
||||
/// Route an agent entry to the appropriate pane.
|
||||
/// Returns None for entries that shouldn't be displayed (memory, system).
|
||||
fn route_entry(entry: &crate::agent::context::ConversationEntry) -> Option<(PaneTarget, String, Marker)> {
|
||||
use crate::agent::api::types::Role;
|
||||
use crate::agent::context::ConversationEntry;
|
||||
|
||||
if let ConversationEntry::Memory { .. } = entry {
|
||||
return None;
|
||||
}
|
||||
|
||||
let msg = entry.message();
|
||||
let text = msg.content_text().to_string();
|
||||
|
||||
match msg.role {
|
||||
Role::User => {
|
||||
if text.starts_with("<system-reminder>") { return None; }
|
||||
Some((PaneTarget::Conversation, text, Marker::User))
|
||||
}
|
||||
Role::Assistant => {
|
||||
// Tool calls → tools pane
|
||||
if let Some(ref calls) = msg.tool_calls {
|
||||
for call in calls {
|
||||
let line = format!("[{}] {}",
|
||||
call.function.name,
|
||||
call.function.arguments.chars().take(80).collect::<String>());
|
||||
// TODO: return multiple targets — for now just return first tool call
|
||||
return Some((PaneTarget::Tools, line, Marker::None));
|
||||
}
|
||||
}
|
||||
if text.is_empty() { return None; }
|
||||
Some((PaneTarget::ConversationAssistant, text, Marker::Assistant))
|
||||
}
|
||||
Role::Tool => {
|
||||
if text.is_empty() { return None; }
|
||||
Some((PaneTarget::ToolResult, text, Marker::None))
|
||||
}
|
||||
Role::System => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct InteractScreen {
|
||||
pub(crate) autonomous: PaneState,
|
||||
pub(crate) conversation: PaneState,
|
||||
|
|
@ -61,69 +108,38 @@ impl InteractScreen {
|
|||
|
||||
/// Sync conversation display from agent entries.
|
||||
fn sync_from_agent(&mut self) {
|
||||
use crate::agent::api::types::Role;
|
||||
|
||||
let agent = self.agent.blocking_lock();
|
||||
let gen = agent.generation;
|
||||
let count = agent.entries().len();
|
||||
|
||||
if gen != self.last_generation {
|
||||
// Generation changed — full re-render
|
||||
// Phase 1: detect desync — pop entries from panes to match
|
||||
if gen != self.last_generation || count < self.last_entry_count {
|
||||
// Compaction or mutation happened — full reset
|
||||
// (could be smarter and pop from front, but reset is safe)
|
||||
self.conversation = PaneState::new(true);
|
||||
self.autonomous = PaneState::new(true);
|
||||
self.tools = PaneState::new(false);
|
||||
self.last_entry_count = 0;
|
||||
}
|
||||
|
||||
// Render new entries
|
||||
// Phase 2: route new entries to panes
|
||||
for entry in agent.entries().iter().skip(self.last_entry_count) {
|
||||
let msg = entry.message();
|
||||
let text = msg.content_text();
|
||||
|
||||
// Skip memory/system-reminder entries — they're context, not conversation
|
||||
if let crate::agent::context::ConversationEntry::Memory { .. } = entry {
|
||||
continue;
|
||||
match route_entry(entry) {
|
||||
Some((PaneTarget::Conversation, text, marker)) => {
|
||||
self.conversation.push_line_with_marker(text, Color::Cyan, marker);
|
||||
}
|
||||
|
||||
match msg.role {
|
||||
Role::User => {
|
||||
// Skip system-reminder injections
|
||||
if text.starts_with("<system-reminder>") {
|
||||
continue;
|
||||
Some((PaneTarget::ConversationAssistant, text, marker)) => {
|
||||
self.conversation.push_line_with_marker(text, Color::Reset, marker);
|
||||
}
|
||||
self.conversation.push_line_with_marker(
|
||||
text.to_string(), Color::Cyan, Marker::User,
|
||||
);
|
||||
Some((PaneTarget::Tools, text, _)) => {
|
||||
self.tools.push_line(text, Color::Yellow);
|
||||
}
|
||||
Role::Assistant => {
|
||||
// Tool calls show in tools pane
|
||||
if let Some(ref calls) = msg.tool_calls {
|
||||
for call in calls {
|
||||
let line = format!("[{}] {}", call.function.name,
|
||||
call.function.arguments.chars().take(80).collect::<String>());
|
||||
self.tools.push_line(line, Color::Yellow);
|
||||
}
|
||||
}
|
||||
// Text content to conversation
|
||||
if !text.is_empty() {
|
||||
self.conversation.push_line_with_marker(
|
||||
text.to_string(), Color::Reset, Marker::Assistant,
|
||||
);
|
||||
}
|
||||
}
|
||||
Role::Tool => {
|
||||
// Tool results to tools pane
|
||||
Some((PaneTarget::ToolResult, text, _)) => {
|
||||
for line in text.lines().take(20) {
|
||||
self.tools.push_line(format!(" {}", line), Color::DarkGray);
|
||||
}
|
||||
if text.lines().count() > 20 {
|
||||
self.tools.push_line(
|
||||
format!(" ... ({} more lines)", text.lines().count() - 20),
|
||||
Color::DarkGray,
|
||||
);
|
||||
}
|
||||
}
|
||||
Role::System => {} // skip
|
||||
None => {} // skip (memory, system, system-reminder)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue