diff --git a/src/NexusReader.Infrastructure/Services/PromptRegistry.cs b/src/NexusReader.Infrastructure/Services/PromptRegistry.cs index 2ba4759..766a75a 100644 --- a/src/NexusReader.Infrastructure/Services/PromptRegistry.cs +++ b/src/NexusReader.Infrastructure/Services/PromptRegistry.cs @@ -17,7 +17,8 @@ public static class PromptRegistry "You are an expert at information architecture. Extract key concepts and their relationships from the text to build a knowledge graph. " + "CRITICAL: Restrict 'label' to a maximum of 3 words. " + "CRITICAL: Extract a MAXIMUM of 15 key concepts/plot points and their relationships. " + - "CRITICAL: Each paragraph in the user text starts with [ID: some-id]. You MUST use these exact IDs as the 'id' for the nodes representing those blocks. " + + "CRITICAL: Each paragraph in the user text starts with [ID: some-id]. Use these IDs ONLY for nodes representing the blocks. " + + "CRITICAL: All other extracted 'concept' nodes MUST have unique, slug-style IDs based on their labels (e.g., 'dependency-injection'). " + "Include a 'current' node representing the block content itself if applicable. " + "CRITICAL: Limit the result to a MAXIMUM of 15 most relevant connections. " + "Return ONLY minified JSON. Schema: { \"graph\": { \"nodes\": [ { \"id\": \"string\", \"label\": \"string\", \"group\": \"concept|current\" } ], \"links\": [ { \"source\": \"string\", \"target\": \"string\", \"value\": 1 } ] } }"; diff --git a/src/NexusReader.UI.Shared/wwwroot/js/knowledgeGraph.js b/src/NexusReader.UI.Shared/wwwroot/js/knowledgeGraph.js index 62db8f0..968e052 100644 --- a/src/NexusReader.UI.Shared/wwwroot/js/knowledgeGraph.js +++ b/src/NexusReader.UI.Shared/wwwroot/js/knowledgeGraph.js @@ -247,6 +247,7 @@ function drag(simulation) { export function setActiveNode(nodeId) { if (!svgElement || !node) return; + // Safety check: ensure we only target the first occurrence if IDs are duplicated const targetNode = node.filter(d => d.id === nodeId); if (targetNode.empty()) { dimNodes(null); @@ -254,20 +255,21 @@ export function setActiveNode(nodeId) { return; } - const d = targetNode.datum(); + const firstMatch = targetNode.filter((d, i) => i === 0); + const d = firstMatch.datum(); // Reset all active classes rootGroup.selectAll(".node-pill").classed("nexus-node-active", false); - targetNode.select(".node-pill").classed("nexus-node-active", true); + firstMatch.select(".node-pill").classed("nexus-node-active", true); // Position badge badge.style("display", "block").datum(d); badge.attr("transform", `translate(${d.x},${d.y})`); - // Dim others + // Dim others (only exact matches for nodeId will be fully opaque) dimNodes(nodeId); - // Smooth transition + // Smooth transition to the first matching node svgElement.transition().duration(1000).call( zoomBehavior.transform, d3.zoomIdentity.translate(width / 2, height / 2).scale(1.2).translate(-d.x, -d.y)