use crate::analyzer::TreeNode;
use crate::parser::ExecutionNode;
use std::collections::HashMap;
use std::rc::Rc;
fn deduplicate_progress(nodes: Vec<ExecutionNode>) -> Vec<ExecutionNode> {
let mut result = Vec::new();
let mut last_agent_id: Option<String> = None;
let mut skip_count = 0;
for node in nodes {
if node.node_type == "progress" {
if let Some(data) = node.extra.as_ref().and_then(|e| e.get("data")) {
if let Some(progress_type) = data.get("type").and_then(|t| t.as_str()) {
if progress_type == "agent_progress" {
if let Some(agent_id) = data.get("agentId").and_then(|a| a.as_str()) {
if last_agent_id.as_deref() == Some(agent_id) {
skip_count += 1;
continue;
} else {
if skip_count > 0 {
skip_count = 0;
}
last_agent_id = Some(agent_id.to_string());
}
}
} else {
last_agent_id = None;
skip_count = 0;
}
}
}
} else {
last_agent_id = None;
skip_count = 0;
}
result.push(node);
}
result
}
fn is_local_command_node(node: &ExecutionNode) -> bool {
if node.node_type != "user" {
return false;
}
let text = match node.message.as_ref() {
Some(m) => m.text_content(),
None => return false,
};
crate::analyzer::prompt_detect::is_local_command_text(&text)
}
fn filter_local_commands(nodes: Vec<ExecutionNode>) -> Vec<ExecutionNode> {
nodes.into_iter().filter(|n| !is_local_command_node(n)).collect()
}
pub fn build_simple_tree(nodes: Vec<ExecutionNode>) -> Vec<TreeNode> {
let nodes = filter_local_commands(nodes);
let nodes = deduplicate_progress(nodes);
let rc_nodes: Vec<Rc<ExecutionNode>> = nodes.into_iter().map(Rc::new).collect();
let mut node_map: HashMap<String, Rc<ExecutionNode>> = HashMap::new();
let mut children_map: HashMap<String, Vec<Rc<ExecutionNode>>> = HashMap::new();
let mut root_nodes: Vec<Rc<ExecutionNode>> = Vec::new();
for rc_node in rc_nodes {
if let Some(ref uuid) = rc_node.uuid {
node_map.insert(uuid.clone(), rc_node);
}
}
for rc_node in node_map.values() {
if let Some(ref parent_uuid) = rc_node.parent_uuid {
children_map
.entry(parent_uuid.clone())
.or_default()
.push(Rc::clone(rc_node));
} else {
root_nodes.push(Rc::clone(rc_node));
}
}
for children in children_map.values_mut() {
children.sort_by(|a, b| {
let ts_a = a.timestamp.unwrap_or(0);
let ts_b = b.timestamp.unwrap_or(0);
ts_a.cmp(&ts_b)
});
}
root_nodes.sort_by(|a, b| {
let ts_a = a.timestamp.unwrap_or(0);
let ts_b = b.timestamp.unwrap_or(0);
ts_a.cmp(&ts_b)
});
root_nodes
.into_iter()
.map(|rc_node| build_tree_node(&rc_node, &children_map, 0))
.collect()
}
fn build_tree_node(
rc_node: &Rc<ExecutionNode>,
children_map: &HashMap<String, Vec<Rc<ExecutionNode>>>,
depth: usize,
) -> TreeNode {
let children = if let Some(ref uuid) = rc_node.uuid {
if let Some(child_nodes) = children_map.get(uuid) {
child_nodes
.iter()
.map(|child_rc| build_tree_node(child_rc, children_map, depth + 1))
.collect()
} else {
Vec::new()
}
} else {
Vec::new()
};
TreeNode {
node: Rc::clone(rc_node),
children,
depth,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::ExecutionNode;
fn create_test_node(uuid: &str, parent_uuid: Option<&str>, node_type: &str) -> ExecutionNode {
ExecutionNode {
uuid: Some(uuid.to_string()),
parent_uuid: parent_uuid.map(|s| s.to_string()),
timestamp: Some(1000),
node_type: node_type.to_string(),
message: None,
tool_use: None,
tool_result: None,
tool_use_result: None,
thinking: None,
progress: None,
token_usage: None,
extra: None,
}
}
#[test]
fn test_build_simple_tree_single_node() {
let nodes = vec![create_test_node("root", None, "user")];
let tree = build_simple_tree(nodes);
assert_eq!(tree.len(), 1);
assert_eq!(tree[0].depth, 0);
assert_eq!(tree[0].children.len(), 0);
assert_eq!(tree[0].node.node_type, "user");
}
#[test]
fn test_build_simple_tree_with_children() {
let nodes = vec![
create_test_node("root", None, "user"),
create_test_node("child1", Some("root"), "assistant"),
create_test_node("child2", Some("root"), "tool_use"),
];
let tree = build_simple_tree(nodes);
assert_eq!(tree.len(), 1);
assert_eq!(tree[0].depth, 0);
assert_eq!(tree[0].children.len(), 2);
assert_eq!(tree[0].children[0].depth, 1);
assert_eq!(tree[0].children[1].depth, 1);
}
#[test]
fn test_build_simple_tree_deep_hierarchy() {
let nodes = vec![
create_test_node("root", None, "user"),
create_test_node("level1", Some("root"), "assistant"),
create_test_node("level2", Some("level1"), "thinking"),
create_test_node("level3", Some("level2"), "tool_use"),
];
let tree = build_simple_tree(nodes);
assert_eq!(tree.len(), 1);
assert_eq!(tree[0].depth, 0);
assert_eq!(tree[0].children[0].depth, 1);
assert_eq!(tree[0].children[0].children[0].depth, 2);
assert_eq!(tree[0].children[0].children[0].children[0].depth, 3);
}
#[test]
fn test_build_simple_tree_multiple_roots() {
let nodes = vec![
create_test_node("root1", None, "user"),
create_test_node("root2", None, "assistant"),
];
let tree = build_simple_tree(nodes);
assert_eq!(tree.len(), 2);
assert_eq!(tree[0].depth, 0);
assert_eq!(tree[1].depth, 0);
}
#[test]
fn test_rc_sharing() {
let nodes = vec![
create_test_node("root", None, "user"),
create_test_node("child", Some("root"), "assistant"),
];
let tree = build_simple_tree(nodes);
assert_eq!(Rc::strong_count(&tree[0].node), 1);
assert_eq!(Rc::strong_count(&tree[0].children[0].node), 1);
}
}