Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions plugins/core/src/graph/duplicate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export function detectDuplicates(graph: Graph, options: DuplicateOptions = {}) {

// Ensure n1 < n2 lexicographically to avoid duplicate pairs
const [a, b] = n1.url < n2.url ? [n1, n2] : [n2, n1];
const pairKey = `${a.url}|${b.url}`;
const pairKey = Graph.getEdgeKey(a.url, b.url);

if (checkedPairs.has(pairKey)) continue;
checkedPairs.add(pairKey);
Expand Down Expand Up @@ -262,7 +262,7 @@ export function detectDuplicates(graph: Graph, options: DuplicateOptions = {}) {
// Skip self-referential edges caused by repointing
if (actualSource === actualTarget) continue;

const edgeKey = `${actualSource}|${actualTarget}`;
const edgeKey = Graph.getEdgeKey(actualSource, actualTarget);
const existingWeight = updatedEdges.get(edgeKey) || 0;
updatedEdges.set(edgeKey, Math.max(existingWeight, edge.weight)); // deduplicate
}
Expand All @@ -276,7 +276,7 @@ export function detectDuplicates(graph: Graph, options: DuplicateOptions = {}) {
node.outLinks = 0;
}
for (const [edgeKey, _weight] of updatedEdges.entries()) {
const [src, tgt] = edgeKey.split('|');
const { source: src, target: tgt } = Graph.parseEdgeKey(edgeKey);
const sn = graph.nodes.get(src);
const tn = graph.nodes.get(tgt);
if (sn) sn.outLinks++;
Expand Down
23 changes: 19 additions & 4 deletions plugins/core/src/graph/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export interface CrawlStats {

export class Graph {
nodes: Map<string, GraphNode> = new Map();
// Using string "source|target" to ensure uniqueness efficiently. Mapping to weight.
// Using JSON string of [source, target] to ensure uniqueness. Mapping to weight.
edges: Map<string, number> = new Map();
limitReached: boolean = false;
sessionStats: CrawlStats = {
Expand All @@ -73,6 +73,21 @@ export class Graph {
duplicateClusters: { id: string; type: 'exact' | 'near' | 'template_heavy'; size: number; representative: string; severity: 'low' | 'medium' | 'high' }[] = [];
contentClusters: ClusterInfo[] = [];

/**
* Generates a unique key for an edge.
*/
static getEdgeKey(source: string, target: string): string {
return JSON.stringify([source, target]);
}

/**
* Parses an edge key back into source and target.
*/
static parseEdgeKey(key: string): { source: string; target: string } {
const [source, target] = JSON.parse(key);
return { source, target };
}

/**
* Adds a node to the graph if it doesn't exist.
* If it exists, updates the status if the new status is non-zero (meaning we crawled it).
Expand Down Expand Up @@ -113,7 +128,7 @@ export class Graph {
const targetNode = this.nodes.get(target);

if (sourceNode && targetNode) {
const edgeKey = `${source}|${target}`;
const edgeKey = Graph.getEdgeKey(source, target);
if (!this.edges.has(edgeKey)) {
this.edges.set(edgeKey, weight);
sourceNode.outLinks++;
Expand All @@ -134,7 +149,7 @@ export class Graph {

getEdges(): GraphEdge[] {
return Array.from(this.edges.entries()).map(([edge, weight]) => {
const [source, target] = edge.split('|');
const { source, target } = Graph.parseEdgeKey(edge);
return { source, target, weight };
});
}
Expand All @@ -157,7 +172,7 @@ export class Graph {
}
if (json.edges) {
for (const edge of json.edges) {
const key = `${edge.source}|${edge.target}`;
const key = Graph.getEdgeKey(edge.source, edge.target);
graph.edges.set(key, edge.weight || 1.0);
}
}
Expand Down
4 changes: 2 additions & 2 deletions plugins/core/tests/duplicate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe('Duplicate Detection', () => {
graph.updateNodeData('https://example.com/b', { contentHash: 'h1' });

// Add edge pointing to B
graph.edges.set('https://example.com/source|https://example.com/b', 1);
graph.addEdge('https://example.com/source', 'https://example.com/b', 1);

// Force A to be the representative by giving it higher inLinks manually, though it's determined dynamically
graph.nodes.get('https://example.com/a')!.inLinks = 10;
Expand All @@ -105,6 +105,6 @@ describe('Duplicate Detection', () => {
expect(b.collapseInto).toBe('https://example.com/a');

// Check edge transfer
expect(graph.edges.has('https://example.com/source|https://example.com/a')).toBe(true);
expect(graph.edges.has(Graph.getEdgeKey('https://example.com/source', 'https://example.com/a'))).toBe(true);
});
});
Loading