Skip to content

Commit ed16da7

Browse files
authored
Merge pull request #18 from AnnulusLabs/fix/large-graph-dashboard-perf
fix: move dagre layout to Web Worker for large graphs
2 parents bc9ab3f + 01d7ece commit ed16da7

File tree

5 files changed

+576
-177
lines changed

5 files changed

+576
-177
lines changed

scripts/generate-large-graph.mjs

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Generate a large fake knowledge graph for testing PR #18
4+
* (Web Worker layout for large graphs).
5+
*
6+
* Usage:
7+
* node scripts/generate-large-graph.mjs [nodeCount]
8+
*
9+
* Default: 3000 nodes. Writes to .understand-anything/knowledge-graph.json
10+
*/
11+
12+
import { writeFileSync, mkdirSync } from "node:fs";
13+
import { resolve } from "node:path";
14+
15+
const NODE_COUNT = parseInt(process.argv[2] || "3000", 10);
16+
const EDGE_RATIO = 1.7; // edges per node (realistic for codebases)
17+
18+
const nodeTypes = ["file", "function", "class", "module", "concept"];
19+
const edgeTypes = [
20+
"imports", "exports", "contains", "inherits", "implements",
21+
"calls", "subscribes", "publishes", "middleware",
22+
"reads_from", "writes_to", "transforms", "validates",
23+
"depends_on", "tested_by", "configures",
24+
"related", "similar_to",
25+
];
26+
const complexities = ["simple", "moderate", "complex"];
27+
const languages = ["TypeScript", "JavaScript", "Python", "Go", "Rust"];
28+
const frameworks = ["React", "Express", "FastAPI", "Gin", "Actix"];
29+
30+
function pick(arr) {
31+
return arr[Math.floor(Math.random() * arr.length)];
32+
}
33+
34+
function generateNodes(count) {
35+
const nodes = [];
36+
for (let i = 0; i < count; i++) {
37+
const type = pick(nodeTypes);
38+
const name = `${type}_${i}`;
39+
nodes.push({
40+
id: `node-${i}`,
41+
type,
42+
name,
43+
filePath: type === "file" ? `src/${name}.ts` : undefined,
44+
summary: `Auto-generated ${type} node #${i} for performance testing.`,
45+
tags: [type, `group-${i % 20}`],
46+
complexity: pick(complexities),
47+
});
48+
}
49+
return nodes;
50+
}
51+
52+
function generateEdges(nodes, edgeCount) {
53+
const edges = [];
54+
const seen = new Set();
55+
const n = nodes.length;
56+
57+
for (let i = 0; i < edgeCount; i++) {
58+
let src, tgt;
59+
// Forward-only edges to avoid cycles (dagre blows the stack on large cyclic graphs)
60+
do {
61+
src = Math.floor(Math.random() * (n - 1));
62+
const offset = Math.floor(Math.random() * Math.min(50, n - src - 1)) + 1;
63+
tgt = src + offset;
64+
} while (tgt >= n || src === tgt || seen.has(`${src}-${tgt}`));
65+
66+
seen.add(`${src}-${tgt}`);
67+
edges.push({
68+
source: nodes[src].id,
69+
target: nodes[tgt].id,
70+
type: pick(edgeTypes),
71+
direction: "forward",
72+
weight: Math.round(Math.random() * 100) / 100,
73+
});
74+
}
75+
return edges;
76+
}
77+
78+
function generateLayers(nodes) {
79+
const layers = [];
80+
const layerNames = [
81+
"Presentation", "Application", "Domain", "Infrastructure",
82+
"API Gateway", "Data Access", "Utilities", "Testing",
83+
];
84+
85+
for (let i = 0; i < layerNames.length; i++) {
86+
const start = Math.floor((i / layerNames.length) * nodes.length);
87+
const end = Math.floor(((i + 1) / layerNames.length) * nodes.length);
88+
layers.push({
89+
id: `layer-${i}`,
90+
name: layerNames[i],
91+
description: `${layerNames[i]} layer (auto-generated)`,
92+
nodeIds: nodes.slice(start, end).map((n) => n.id),
93+
});
94+
}
95+
return layers;
96+
}
97+
98+
function generateTour(nodes) {
99+
const steps = [];
100+
const stepCount = Math.min(8, Math.floor(nodes.length / 100));
101+
for (let i = 0; i < stepCount; i++) {
102+
const idx = Math.floor((i / stepCount) * nodes.length);
103+
steps.push({
104+
order: i + 1,
105+
title: `Step ${i + 1}: Explore ${nodes[idx].name}`,
106+
description: `This tour step highlights node **${nodes[idx].name}** and its surrounding context.`,
107+
nodeIds: [nodes[idx].id, nodes[Math.min(idx + 1, nodes.length - 1)].id],
108+
});
109+
}
110+
return steps;
111+
}
112+
113+
// ── Generate ──
114+
115+
const nodes = generateNodes(NODE_COUNT);
116+
const edgeCount = Math.floor(NODE_COUNT * EDGE_RATIO);
117+
const edges = generateEdges(nodes, edgeCount);
118+
const layers = generateLayers(nodes);
119+
const tour = generateTour(nodes);
120+
121+
const graph = {
122+
version: "1.0",
123+
project: {
124+
name: "large-test-project",
125+
languages: languages.slice(0, 3),
126+
frameworks: frameworks.slice(0, 2),
127+
description: `Auto-generated project with ${NODE_COUNT} nodes for performance testing.`,
128+
analyzedAt: new Date().toISOString(),
129+
gitCommitHash: "0000000000000000000000000000000000000000",
130+
},
131+
nodes,
132+
edges,
133+
layers,
134+
tour,
135+
};
136+
137+
const outDir = resolve(process.cwd(), ".understand-anything");
138+
mkdirSync(outDir, { recursive: true });
139+
const outPath = resolve(outDir, "knowledge-graph.json");
140+
writeFileSync(outPath, JSON.stringify(graph, null, 2));
141+
142+
console.log(`Generated knowledge graph:`);
143+
console.log(` Nodes: ${nodes.length}`);
144+
console.log(` Edges: ${edges.length}`);
145+
console.log(` Layers: ${layers.length}`);
146+
console.log(` Tour steps: ${tour.length}`);
147+
console.log(` Written to: ${outPath}`);

understand-anything-plugin/packages/dashboard/src/components/CustomNode.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { memo } from "react";
12
import { Handle, Position } from "@xyflow/react";
23
import type { NodeProps, Node } from "@xyflow/react";
34

@@ -40,7 +41,7 @@ export interface CustomNodeData extends Record<string, unknown> {
4041

4142
export type CustomFlowNode = Node<CustomNodeData, "custom">;
4243

43-
export default function CustomNode({
44+
function CustomNodeComponent({
4445
id,
4546
data,
4647
}: NodeProps<CustomFlowNode>) {
@@ -122,3 +123,6 @@ export default function CustomNode({
122123
</div>
123124
);
124125
}
126+
127+
const CustomNode = memo(CustomNodeComponent);
128+
export default CustomNode;

0 commit comments

Comments
 (0)