diff --git a/src-tauri/src/api_server.rs b/src-tauri/src/api_server.rs index e9c1d2810..bbed529e8 100644 --- a/src-tauri/src/api_server.rs +++ b/src-tauri/src/api_server.rs @@ -1218,6 +1218,15 @@ struct ApiGraphEdge { weight: f64, } +#[derive(Debug, Clone)] +struct RawGraphNode { + id: String, + label: String, + node_type: String, + path: String, + links: Vec, +} + fn handle_graph(app: &AppHandle, project_id: &str, query: &str) -> ApiResponse { let project = match resolve_project(app, project_id) { Ok(project) => project, @@ -1232,43 +1241,44 @@ fn handle_graph(app: &AppHandle, project_id: &str, query: &str) -> ApiResponse { .unwrap_or(200) .clamp(1, 1000); - match build_graph(&project.path) { - Ok((mut nodes, edges)) => { - if let Some(ref q) = q { - nodes.retain(|n| { - n.id.to_lowercase().contains(q) || n.label.to_lowercase().contains(q) - }); - } - if let Some(ref node_type) = node_type { - nodes.retain(|n| n.node_type == *node_type); - } - nodes.truncate(limit); - let ids: BTreeSet = nodes.iter().map(|n| n.id.clone()).collect(); - let edges: Vec = edges - .into_iter() - .filter(|e| ids.contains(&e.source) && ids.contains(&e.target)) - .collect(); + match build_graph(&project.path, q.as_deref(), node_type.as_deref(), limit) { + Ok((nodes, edges)) => { ok(json!({ "ok": true, "projectId": project.id, "nodes": nodes, "edges": edges })) } Err(e) => err(500, e), } } -fn build_graph(project_path: &str) -> Result<(Vec, Vec), String> { +fn build_graph( + project_path: &str, + q: Option<&str>, + node_type: Option<&str>, + limit: usize, +) -> Result<(Vec, Vec), String> { let wiki_root = Path::new(project_path).join("wiki"); - let mut raw: BTreeMap)> = BTreeMap::new(); - for entry in WalkDir::new(&wiki_root).into_iter().filter_map(Result::ok) { - if !entry.file_type().is_file() - || entry.path().extension().and_then(|s| s.to_str()) != Some("md") - { - continue; - } - let content = match fs::read_to_string(entry.path()) { + let mut files: Vec = WalkDir::new(&wiki_root) + .into_iter() + .filter_map(Result::ok) + .filter(|entry| { + entry.file_type().is_file() + && entry.path().extension().and_then(|s| s.to_str()) == Some("md") + }) + .map(|entry| entry.path().to_path_buf()) + .collect(); + files.sort_by_key(|path| { + path.file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("") + .to_lowercase() + }); + + let mut raw: Vec = Vec::new(); + for path in files { + let content = match fs::read_to_string(&path) { Ok(content) => content, Err(_) => continue, }; - let id = entry - .path() + let id = path .file_stem() .and_then(|s| s.to_str()) .unwrap_or("") @@ -1276,35 +1286,62 @@ fn build_graph(project_path: &str) -> Result<(Vec, Vec= limit { + break; + } } - let ids: BTreeSet = raw.keys().cloned().collect(); - let mut link_count: BTreeMap = raw.keys().map(|id| (id.clone(), 0)).collect(); + let ids: BTreeSet = raw.iter().map(|node| node.id.clone()).collect(); + let mut link_count: BTreeMap = + ids.iter().map(|id| (id.clone(), 0)).collect(); let mut seen = BTreeSet::new(); let mut edges = Vec::new(); - for (source, (_, _, _, links)) in &raw { - for link in links { + for node in &raw { + for link in &node.links { let Some(target) = resolve_link(link, &ids) else { continue; }; - if &target == source { + if target == node.id { continue; } - let key = if source < &target { - format!("{source}::{target}") + let key = if node.id < target { + format!("{}::{target}", node.id) } else { - format!("{target}::{source}") + format!("{target}::{}", node.id) }; if seen.insert(key) { - *link_count.entry(source.clone()).or_default() += 1; + *link_count.entry(node.id.clone()).or_default() += 1; *link_count.entry(target.clone()).or_default() += 1; edges.push(ApiGraphEdge { - source: source.clone(), + source: node.id.clone(), target, weight: 1.0, }); @@ -1313,13 +1350,12 @@ fn build_graph(project_path: &str) -> Result<(Vec, Vec