mod colors;
mod error;
mod html;
mod paths;
mod serialize;
pub use error::VisualizationError;
use std::path::{Path, PathBuf};
use cognee_graph::GraphDBTrait;
use tokio::fs;
use tokio::io::AsyncWriteExt;
use tracing::info;
pub async fn visualize(
graph_db: &dyn GraphDBTrait,
output_path: Option<&Path>,
) -> Result<PathBuf, VisualizationError> {
let html = render(graph_db).await?;
let dest: PathBuf = match output_path {
Some(p) => p.to_path_buf(),
None => paths::default_output_path()?,
};
if let Some(parent) = dest.parent()
&& !parent.as_os_str().is_empty()
{
fs::create_dir_all(parent).await?;
}
let mut file = fs::File::create(&dest).await?;
file.write_all(html.as_bytes()).await?;
file.flush().await?;
info!(path = %dest.display(), "Graph visualization saved");
Ok(dest)
}
pub async fn render(graph_db: &dyn GraphDBTrait) -> Result<String, VisualizationError> {
let (nodes, edges) = graph_db.get_graph_data().await?;
let serialized = serialize::serialize_graph(nodes, edges);
html::build_html(&serialized, None)
}
pub async fn render_multi_user(
pairs: &[(String, std::sync::Arc<dyn GraphDBTrait>)],
) -> Result<String, VisualizationError> {
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
let mut all_nodes: HashMap<String, cognee_graph::GraphNode> = HashMap::new();
let mut node_order: Vec<String> = Vec::new();
let mut all_edges: Vec<cognee_graph::EdgeData> = Vec::new();
let mut seen_edges: HashSet<(String, String, String)> = HashSet::new();
for (user_label, gdb) in pairs {
let (nodes, edges) = gdb.get_graph_data().await?;
for (node_id, mut node_info) in nodes {
let key = node_id.to_string();
if all_nodes.contains_key(&key) {
continue;
}
let needs_label = match node_info.get("source_user") {
Some(serde_json::Value::String(s)) if !s.is_empty() => false,
Some(serde_json::Value::Null) | None => true,
_ => false,
};
if needs_label {
node_info.insert(
Cow::Borrowed("source_user"),
serde_json::Value::String(user_label.clone()),
);
}
node_order.push(key.clone());
all_nodes.insert(key, (node_id, node_info));
}
for edge in edges {
let edge_key = (edge.0.to_string(), edge.1.to_string(), edge.2.clone());
if seen_edges.insert(edge_key) {
all_edges.push(edge);
}
}
}
let ordered_nodes: Vec<cognee_graph::GraphNode> = node_order
.into_iter()
.filter_map(|k| all_nodes.remove(&k))
.collect();
let serialized = serialize::serialize_graph(ordered_nodes, all_edges);
html::build_html(&serialized, None)
}