Files
deusdata--codebase-memory-mcp/graph-ui/src/components/EdgeLines.tsx
T
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

155 lines
4.8 KiB
TypeScript

import { useMemo } from "react";
import * as THREE from "three";
import type { GraphNode, GraphEdge } from "../lib/types";
import { edgeIntensityScale } from "../lib/density";
interface EdgeLinesProps {
nodes: GraphNode[];
edges: GraphEdge[];
highlightedIds: Set<number> | null;
opacity?: number;
/* User edge-brightness multiplier (see DisplaySettings). Layered on top of
* the automatic density scale. */
brightness?: number;
/* When set, edge.target is looked up in this array instead of `nodes`.
* Used for cross-galaxy edges where source lives in the primary graph
* and target lives in a linked project's offset-adjusted nodes. */
targetNodes?: GraphNode[];
}
function getClusterKey(fp?: string): string {
if (!fp) return "";
const parts = fp.split("/");
return parts.slice(0, Math.min(2, parts.length)).join("/");
}
/* Edge type → color (matches the filter panel) */
const EDGE_TYPE_COLORS: Record<string, string> = {
CALLS: "#1DA27E",
IMPORTS: "#3b82f6",
DEFINES: "#a855f7",
DEFINES_METHOD: "#a855f7",
CONTAINS_FILE: "#22c55e",
CONTAINS_FOLDER: "#22c55e",
CONTAINS_PACKAGE: "#22c55e",
HANDLES: "#eab308",
IMPLEMENTS: "#f97316",
HTTP_CALLS: "#e11d48",
ASYNC_CALLS: "#ec4899",
GRPC_CALLS: "#f59e0b",
GRAPHQL_CALLS: "#e879f9",
TRPC_CALLS: "#a78bfa",
CROSS_HTTP_CALLS: "#fb923c",
CROSS_ASYNC_CALLS: "#fb7185",
CROSS_GRPC_CALLS: "#fbbf24",
CROSS_GRAPHQL_CALLS: "#f0abfc",
CROSS_TRPC_CALLS: "#c4b5fd",
CROSS_CHANNEL: "#fdba74",
MEMBER_OF: "#64748b",
TESTS_FILE: "#06b6d4",
};
const DEFAULT_EDGE_COLOR = "#1C8585";
export function EdgeLines({
nodes,
edges,
highlightedIds,
opacity = 1.0,
brightness = 1.0,
targetNodes,
}: EdgeLinesProps) {
const geometry = useMemo(() => {
/* Shrink per-edge glow as the edge count grows so the additively-blended
* center doesn't saturate to white; the user multiplier rides on top. */
const densityScale = edgeIntensityScale(edges.length) * brightness;
const srcMap = new Map<number, number>();
for (let i = 0; i < nodes.length; i++) {
srcMap.set(nodes[i].id, i);
}
const tgtArr = targetNodes ?? nodes;
const tgtMap = targetNodes ? new Map<number, number>() : srcMap;
if (targetNodes) {
for (let i = 0; i < targetNodes.length; i++) {
tgtMap.set(targetNodes[i].id, i);
}
}
const hasHighlight = highlightedIds && highlightedIds.size > 0;
const positions = new Float32Array(edges.length * 6);
const colors = new Float32Array(edges.length * 6);
let validCount = 0;
for (const edge of edges) {
const si = srcMap.get(edge.source);
const ti = tgtMap.get(edge.target);
if (si === undefined || ti === undefined) continue;
const s = nodes[si];
const t = tgtArr[ti];
const sHL = !hasHighlight || highlightedIds.has(s.id);
const tHL = !hasHighlight || highlightedIds.has(t.id);
if (hasHighlight && !sHL && !tHL) continue;
const sameCluster =
getClusterKey(s.file_path) === getClusterKey(t.file_path);
/* Intensity based on cluster membership and highlight.
* With additive blending + dark background, these glow nicely. */
let intensity = sameCluster ? 0.25 : 0.06;
if (hasHighlight) {
/* A selection stays at full strength (never density-scaled) so it
* pops against the dimmed rest; only the un-selected bulk is scaled. */
intensity = sHL && tHL ? 0.5 : 0.04 * densityScale;
} else {
intensity *= densityScale;
}
const off = validCount * 6;
positions[off] = s.x;
positions[off + 1] = s.y;
positions[off + 2] = s.z;
positions[off + 3] = t.x;
positions[off + 4] = t.y;
positions[off + 5] = t.z;
/* Color from edge TYPE (correlates with edge type filter) */
const edgeColor = new THREE.Color(
EDGE_TYPE_COLORS[edge.type] ?? DEFAULT_EDGE_COLOR,
);
colors[off] = edgeColor.r * intensity;
colors[off + 1] = edgeColor.g * intensity;
colors[off + 2] = edgeColor.b * intensity;
colors[off + 3] = edgeColor.r * intensity;
colors[off + 4] = edgeColor.g * intensity;
colors[off + 5] = edgeColor.b * intensity;
validCount++;
}
const geo = new THREE.BufferGeometry();
geo.setAttribute(
"position",
new THREE.BufferAttribute(positions.slice(0, validCount * 6), 3),
);
geo.setAttribute(
"color",
new THREE.BufferAttribute(colors.slice(0, validCount * 6), 3),
);
return geo;
}, [nodes, edges, highlightedIds, targetNodes, brightness]);
return (
<lineSegments geometry={geometry}>
<lineBasicMaterial
vertexColors
transparent
opacity={opacity}
blending={THREE.AdditiveBlending}
depthWrite={false}
toneMapped={false}
/>
</lineSegments>
);
}