use anyhow::Result;
use clap::ArgMatches;
use dakera_client::knowledge::{
DeduplicateRequest, FullKnowledgeGraphRequest, KnowledgeGraphRequest, SummarizeRequest,
};
use dakera_client::DakeraClient;
use serde::Serialize;
use crate::output;
use crate::OutputFormat;
#[derive(Debug, Serialize)]
pub struct NodeRow {
pub id: String,
pub content: String,
pub memory_type: String,
pub importance: String,
}
#[derive(Debug, Serialize)]
pub struct EdgeRow {
pub source: String,
pub target: String,
pub similarity: f32,
pub relationship: String,
}
#[derive(Debug, Serialize)]
pub struct DuplicateGroupRow {
pub group: usize,
pub memory_ids: String,
}
pub async fn execute(url: &str, matches: &ArgMatches, format: OutputFormat) -> Result<()> {
let client = DakeraClient::new(url)?;
match matches.subcommand() {
Some(("graph", sub_matches)) => {
let agent_id = sub_matches.get_one::<String>("agent_id").unwrap();
let memory_id = sub_matches.get_one::<String>("memory-id").cloned();
let depth = sub_matches.get_one::<u32>("depth").copied();
let min_similarity = sub_matches.get_one::<f32>("min-similarity").copied();
let request = KnowledgeGraphRequest {
agent_id: agent_id.clone(),
memory_id,
depth,
min_similarity,
};
let response = client.knowledge_graph(request).await?;
output::info(&format!(
"Knowledge graph: {} nodes, {} edges",
response.nodes.len(),
response.edges.len()
));
if !response.nodes.is_empty() {
let rows: Vec<NodeRow> = response
.nodes
.into_iter()
.map(|n| NodeRow {
id: n.id,
content: if n.content.len() > 80 {
format!("{}...", &n.content[..77])
} else {
n.content
},
memory_type: n.memory_type.unwrap_or_else(|| "-".to_string()),
importance: n
.importance
.map(|v| format!("{:.3}", v))
.unwrap_or_else(|| "-".to_string()),
})
.collect();
output::print_data(&rows, format);
}
if !response.edges.is_empty() {
println!();
output::info("Edges:");
let edge_rows: Vec<EdgeRow> = response
.edges
.into_iter()
.map(|e| EdgeRow {
source: e.source,
target: e.target,
similarity: e.similarity,
relationship: e.relationship.unwrap_or_else(|| "-".to_string()),
})
.collect();
output::print_data(&edge_rows, format);
}
if let Some(clusters) = response.clusters {
if !clusters.is_empty() {
println!();
output::info(&format!("Found {} clusters", clusters.len()));
for (i, cluster) in clusters.iter().enumerate() {
println!(" Cluster {}: {} nodes", i + 1, cluster.len());
}
}
}
}
Some(("full-graph", sub_matches)) => {
let agent_id = sub_matches.get_one::<String>("agent_id").unwrap();
let max_nodes = sub_matches.get_one::<u32>("max-nodes").copied();
let min_similarity = sub_matches.get_one::<f32>("min-similarity").copied();
let cluster_threshold = sub_matches.get_one::<f32>("cluster-threshold").copied();
let max_edges = sub_matches.get_one::<u32>("max-edges").copied();
let request = FullKnowledgeGraphRequest {
agent_id: agent_id.clone(),
max_nodes,
min_similarity,
cluster_threshold,
max_edges_per_node: max_edges,
};
let response = client.full_knowledge_graph(request).await?;
output::info(&format!(
"Full knowledge graph for '{}': {} nodes, {} edges",
agent_id,
response.nodes.len(),
response.edges.len()
));
if !response.nodes.is_empty() {
let rows: Vec<NodeRow> = response
.nodes
.into_iter()
.map(|n| NodeRow {
id: n.id,
content: if n.content.len() > 80 {
format!("{}...", &n.content[..77])
} else {
n.content
},
memory_type: n.memory_type.unwrap_or_else(|| "-".to_string()),
importance: n
.importance
.map(|v| format!("{:.3}", v))
.unwrap_or_else(|| "-".to_string()),
})
.collect();
output::print_data(&rows, format);
}
if !response.edges.is_empty() {
println!();
output::info("Edges:");
let edge_rows: Vec<EdgeRow> = response
.edges
.into_iter()
.map(|e| EdgeRow {
source: e.source,
target: e.target,
similarity: e.similarity,
relationship: e.relationship.unwrap_or_else(|| "-".to_string()),
})
.collect();
output::print_data(&edge_rows, format);
}
if let Some(clusters) = response.clusters {
if !clusters.is_empty() {
println!();
output::info(&format!("Found {} clusters", clusters.len()));
for (i, cluster) in clusters.iter().enumerate() {
println!(" Cluster {}: {} nodes", i + 1, cluster.len());
}
}
}
}
Some(("summarize", sub_matches)) => {
let agent_id = sub_matches.get_one::<String>("agent_id").unwrap();
let memory_ids = sub_matches
.get_one::<String>("memory-ids")
.map(|s| s.split(',').map(|id| id.trim().to_string()).collect());
let target_type = sub_matches.get_one::<String>("target-type").cloned();
let dry_run = sub_matches.get_flag("dry-run");
let request = SummarizeRequest {
agent_id: agent_id.clone(),
memory_ids,
target_type,
dry_run,
};
let response = client.summarize(request).await?;
if dry_run {
output::info(&format!(
"[dry-run] Would summarize {} source memories",
response.source_count
));
println!();
println!("Preview:");
println!("{}", response.summary);
} else {
output::success(&format!("Summarized {} memories", response.source_count));
if let Some(ref id) = response.new_memory_id {
output::info(&format!("New memory ID: {}", id));
}
println!();
println!("{}", response.summary);
}
}
Some(("deduplicate", sub_matches)) => {
let agent_id = sub_matches.get_one::<String>("agent_id").unwrap();
let threshold = sub_matches.get_one::<f32>("threshold").copied();
let memory_type = sub_matches.get_one::<String>("type").cloned();
let dry_run = sub_matches.get_flag("dry-run");
let request = DeduplicateRequest {
agent_id: agent_id.clone(),
threshold,
memory_type,
dry_run,
};
let response = client.deduplicate(request).await?;
if dry_run {
output::info(&format!(
"[dry-run] Found {} duplicates in {} groups (would remove {})",
response.duplicates_found,
response.groups.len(),
response.removed_count
));
} else {
output::success(&format!(
"Found {} duplicates, removed {}",
response.duplicates_found, response.removed_count
));
}
if !response.groups.is_empty() {
println!();
output::info("Duplicate groups:");
let rows: Vec<DuplicateGroupRow> = response
.groups
.into_iter()
.enumerate()
.map(|(i, group)| DuplicateGroupRow {
group: i + 1,
memory_ids: group.join(", "),
})
.collect();
output::print_data(&rows, format);
}
}
_ => {
output::error("Unknown knowledge subcommand. Use --help for usage.");
std::process::exit(1);
}
}
Ok(())
}