noteconnection 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +198 -0
- package/dist/backend/CommunityDetection.js +58 -0
- package/dist/backend/FileLoader.js +110 -0
- package/dist/backend/GraphBuilder.js +347 -0
- package/dist/backend/GraphMetrics.js +70 -0
- package/dist/backend/algorithms/CycleDetection.js +63 -0
- package/dist/backend/algorithms/HybridEngine.js +70 -0
- package/dist/backend/algorithms/StatisticalAnalyzer.js +123 -0
- package/dist/backend/algorithms/TopologicalSort.js +69 -0
- package/dist/backend/algorithms/VectorSpace.js +87 -0
- package/dist/backend/build_dag.js +164 -0
- package/dist/backend/config.js +17 -0
- package/dist/backend/graph.js +108 -0
- package/dist/backend/main.js +67 -0
- package/dist/backend/parser.js +94 -0
- package/dist/backend/test_robustness/test_hybrid.js +60 -0
- package/dist/backend/test_robustness/test_statistics.js +58 -0
- package/dist/backend/test_robustness/test_vector.js +54 -0
- package/dist/backend/test_robustness.js +113 -0
- package/dist/backend/types.js +3 -0
- package/dist/backend/utils/frontmatterParser.js +121 -0
- package/dist/backend/utils/stringUtils.js +66 -0
- package/dist/backend/workers/keywordMatchWorker.js +22 -0
- package/dist/core/Graph.js +121 -0
- package/dist/core/Graph.test.js +37 -0
- package/dist/core/types.js +2 -0
- package/dist/frontend/analysis.js +356 -0
- package/dist/frontend/app.js +1447 -0
- package/dist/frontend/data.js +8356 -0
- package/dist/frontend/graph_data.json +8356 -0
- package/dist/frontend/index.html +279 -0
- package/dist/frontend/reader.js +177 -0
- package/dist/frontend/settings.js +84 -0
- package/dist/frontend/source_manager.js +61 -0
- package/dist/frontend/styles.css +577 -0
- package/dist/frontend/styles_analysis.css +145 -0
- package/dist/index.js +121 -0
- package/dist/server.js +149 -0
- package/package.json +39 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const path = __importStar(require("path"));
|
|
37
|
+
const FileLoader_1 = require("../FileLoader");
|
|
38
|
+
const StatisticalAnalyzer_1 = require("../algorithms/StatisticalAnalyzer");
|
|
39
|
+
async function run() {
|
|
40
|
+
const root = path.resolve(__dirname, '../../../testconcept');
|
|
41
|
+
console.log(`Loading files from: ${root}`);
|
|
42
|
+
const files = await FileLoader_1.FileLoader.loadFiles(root);
|
|
43
|
+
console.log(`Loaded ${files.length} files.`);
|
|
44
|
+
const terms = files.map(f => f.filename);
|
|
45
|
+
console.log(`Analyzing ${terms.length} terms...`);
|
|
46
|
+
const matrix = StatisticalAnalyzer_1.StatisticalAnalyzer.analyze(files, terms);
|
|
47
|
+
// Low thresholds for demonstration on small dataset
|
|
48
|
+
const minSupport = 0.05;
|
|
49
|
+
const asymmetry = 0.1;
|
|
50
|
+
console.log(`Inferring dependencies (Min Support: ${minSupport}, Asymmetry: ${asymmetry})...`);
|
|
51
|
+
const deps = StatisticalAnalyzer_1.StatisticalAnalyzer.inferDependencies(matrix, minSupport, asymmetry);
|
|
52
|
+
console.log(`Inferred ${deps.length} dependencies.`);
|
|
53
|
+
console.log('Top 20 Suggestions:');
|
|
54
|
+
deps.slice(0, 20).forEach(d => {
|
|
55
|
+
console.log(`[${d.source}] -> [${d.target}] (Conf: ${d.confidence.toFixed(2)}, Jaccard: ${d.weight.toFixed(2)})`);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
run().catch(e => console.error(e));
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const path = __importStar(require("path"));
|
|
37
|
+
const FileLoader_1 = require("../FileLoader");
|
|
38
|
+
const VectorSpace_1 = require("../algorithms/VectorSpace");
|
|
39
|
+
async function run() {
|
|
40
|
+
const root = path.resolve(__dirname, '../../../testconcept');
|
|
41
|
+
console.log(`Loading files from: ${root}`);
|
|
42
|
+
const files = await FileLoader_1.FileLoader.loadFiles(root);
|
|
43
|
+
console.log(`Loaded ${files.length} files.`);
|
|
44
|
+
const vectorSpace = new VectorSpace_1.VectorSpace(files);
|
|
45
|
+
console.log('Vector Space built.');
|
|
46
|
+
// Test a specific file
|
|
47
|
+
const testFile = files[0].filename; // e.g. "Absorption"
|
|
48
|
+
console.log(`Finding similarities for: [${testFile}]`);
|
|
49
|
+
const similar = vectorSpace.getSimilar(testFile, 5);
|
|
50
|
+
similar.forEach(res => {
|
|
51
|
+
console.log(` -> [${res.id}] Score: ${res.score.toFixed(4)}`);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
run().catch(e => console.error(e));
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
// src/backend/test_robustness.ts
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const parser_1 = require("./parser");
|
|
39
|
+
const graph_1 = require("./graph");
|
|
40
|
+
async function test() {
|
|
41
|
+
const inputDir = path.resolve(__dirname, '../../test_robustness');
|
|
42
|
+
const outputDir = path.resolve(__dirname, '../../test_robustness');
|
|
43
|
+
const outputFile = path.join(outputDir, 'test_graph_result.json');
|
|
44
|
+
console.log(`Starting Robustness Test...`);
|
|
45
|
+
// 1. Load Files
|
|
46
|
+
// 1. 加载文件
|
|
47
|
+
const rawFiles = await parser_1.FileLoader.loadFiles(inputDir);
|
|
48
|
+
console.log(`Loaded ${rawFiles.length} files.`);
|
|
49
|
+
// 2. Parse Concepts
|
|
50
|
+
// 2. 解析概念
|
|
51
|
+
const concepts = parser_1.NoteParser.parse(rawFiles);
|
|
52
|
+
// 3. Build Graph
|
|
53
|
+
// 3. 构建图
|
|
54
|
+
const graph = graph_1.GraphBuilder.buildGraph(concepts);
|
|
55
|
+
// 4. Export
|
|
56
|
+
// 4. 导出
|
|
57
|
+
await graph_1.Exporter.exportToJSON(graph, outputFile);
|
|
58
|
+
// 5. Validation Logic
|
|
59
|
+
// 5. 验证逻辑
|
|
60
|
+
console.log('--- Validation Report ---');
|
|
61
|
+
// Check Node Count
|
|
62
|
+
// 检查节点数量
|
|
63
|
+
if (graph.nodes.length === 6) {
|
|
64
|
+
console.log('[PASS] Node Count is 6');
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
console.error(`[FAIL] Node Count is ${graph.nodes.length}, expected 6`);
|
|
68
|
+
}
|
|
69
|
+
// Check Cycle A->B->C->A
|
|
70
|
+
// 检查循环 A->B->C->A
|
|
71
|
+
// Logic: Concept A mentions B -> Edge B->A (B is prerequisite for A)
|
|
72
|
+
// 逻辑:概念 A 提到 B -> 边 B->A (B 是 A 的先决条件)
|
|
73
|
+
// Concept B mentions C -> Edge C->B
|
|
74
|
+
// Concept C mentions A -> Edge A->C
|
|
75
|
+
// Cycle: A->C->B->A
|
|
76
|
+
const hasEdge = (src, tgt) => graph.edges.some(e => e.source === src && e.target === tgt);
|
|
77
|
+
if (hasEdge('Concept B', 'Concept A'))
|
|
78
|
+
console.log('[PASS] Edge B->A exists');
|
|
79
|
+
else
|
|
80
|
+
console.error('[FAIL] Edge B->A missing');
|
|
81
|
+
if (hasEdge('Concept C', 'Concept B'))
|
|
82
|
+
console.log('[PASS] Edge C->B exists');
|
|
83
|
+
else
|
|
84
|
+
console.error('[FAIL] Edge C->B missing');
|
|
85
|
+
if (hasEdge('Concept A', 'Concept C'))
|
|
86
|
+
console.log('[PASS] Edge A->C exists');
|
|
87
|
+
else
|
|
88
|
+
console.error('[FAIL] Edge A->C missing');
|
|
89
|
+
// Check Special Char
|
|
90
|
+
// 检查特殊字符
|
|
91
|
+
// Special mentions A -> A->Special
|
|
92
|
+
if (hasEdge('Concept A', 'Special (Char) Concept'))
|
|
93
|
+
console.log('[PASS] Edge A->Special exists');
|
|
94
|
+
else
|
|
95
|
+
console.error('[FAIL] Edge A->Special missing');
|
|
96
|
+
// Check Case Insensitivity
|
|
97
|
+
// 检查不区分大小写
|
|
98
|
+
// D mentions "concept a" -> A->D
|
|
99
|
+
if (hasEdge('Concept A', 'Concept D'))
|
|
100
|
+
console.log('[PASS] Edge A->D (Case Insensitive) exists');
|
|
101
|
+
else
|
|
102
|
+
console.error('[FAIL] Edge A->D missing');
|
|
103
|
+
// Check Empty Concept
|
|
104
|
+
// 检查空概念
|
|
105
|
+
// Should be a node, but no edges connected to/from it (unless its name is common)
|
|
106
|
+
// 应该是一个节点,但没有边连接到它/从它连接(除非它的名字很普通)
|
|
107
|
+
const emptyNode = graph.nodes.find(n => n.id === 'Empty Concept');
|
|
108
|
+
if (emptyNode)
|
|
109
|
+
console.log('[PASS] Empty Concept node exists');
|
|
110
|
+
else
|
|
111
|
+
console.error('[FAIL] Empty Concept node missing');
|
|
112
|
+
}
|
|
113
|
+
test().catch(err => console.error(err));
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FrontmatterParser = void 0;
|
|
4
|
+
class FrontmatterParser {
|
|
5
|
+
/**
|
|
6
|
+
* Parses YAML frontmatter to extract structured metadata.
|
|
7
|
+
* 解析 YAML frontmatter 以提取结构化元数据。
|
|
8
|
+
*/
|
|
9
|
+
static parse(content) {
|
|
10
|
+
const metadata = {
|
|
11
|
+
tags: [],
|
|
12
|
+
prerequisites: [],
|
|
13
|
+
next: []
|
|
14
|
+
};
|
|
15
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
16
|
+
if (!match)
|
|
17
|
+
return metadata;
|
|
18
|
+
const yaml = match[1];
|
|
19
|
+
// Helper to extract list or inline array
|
|
20
|
+
const extractField = (fieldName) => {
|
|
21
|
+
const results = [];
|
|
22
|
+
// 1. Check for List format (starts with newline or empty value)
|
|
23
|
+
// field:
|
|
24
|
+
// - item
|
|
25
|
+
const listBlockRegex = new RegExp(`${fieldName}:\\s*\\r?\\n((?:\\s*-\\s*.*\\r?\\n?)*)`, 'i');
|
|
26
|
+
const listMatch = yaml.match(listBlockRegex);
|
|
27
|
+
if (listMatch) {
|
|
28
|
+
const lines = listMatch[1].split(/\r?\n/);
|
|
29
|
+
lines.forEach(line => {
|
|
30
|
+
const itemMatch = line.match(/\s*-\s*(.*)/);
|
|
31
|
+
if (itemMatch) {
|
|
32
|
+
const clean = this.cleanLink(itemMatch[1]);
|
|
33
|
+
if (clean)
|
|
34
|
+
results.push(clean);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
// If we found a list, return it. Prioritize list over inline if both exist (rare but possible in bad yaml)
|
|
38
|
+
if (results.length > 0)
|
|
39
|
+
return results;
|
|
40
|
+
}
|
|
41
|
+
// 2. Check for Inline value (Array or Single)
|
|
42
|
+
const inlineRegex = new RegExp(`${fieldName}:\\s*(.*)`, 'i');
|
|
43
|
+
const inlineMatch = yaml.match(inlineRegex);
|
|
44
|
+
if (inlineMatch) {
|
|
45
|
+
let val = inlineMatch[1].trim();
|
|
46
|
+
if (!val)
|
|
47
|
+
return results; // handled by list or empty
|
|
48
|
+
// Check if it's a comment or invalid
|
|
49
|
+
if (val.startsWith('#'))
|
|
50
|
+
return results;
|
|
51
|
+
// Case A: WikiLink "[[Link]]" -> Single Item
|
|
52
|
+
if (val.startsWith('[[')) {
|
|
53
|
+
const clean = this.cleanLink(val);
|
|
54
|
+
if (clean)
|
|
55
|
+
results.push(clean);
|
|
56
|
+
return results;
|
|
57
|
+
}
|
|
58
|
+
// Case B: Inline Array "[a, b]"
|
|
59
|
+
if (val.startsWith('[')) {
|
|
60
|
+
// Remove outer [ ]
|
|
61
|
+
const inner = val.replace(/^\[|\]$/g, '');
|
|
62
|
+
inner.split(',').forEach(item => {
|
|
63
|
+
const clean = this.cleanLink(item);
|
|
64
|
+
if (clean)
|
|
65
|
+
results.push(clean);
|
|
66
|
+
});
|
|
67
|
+
return results;
|
|
68
|
+
}
|
|
69
|
+
// Case C: Plain Single Value "Item"
|
|
70
|
+
// Ensure it's not a list start (dash)
|
|
71
|
+
if (!val.startsWith('-')) {
|
|
72
|
+
const clean = this.cleanLink(val);
|
|
73
|
+
if (clean)
|
|
74
|
+
results.push(clean);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return results;
|
|
78
|
+
};
|
|
79
|
+
metadata.tags = extractField('tags');
|
|
80
|
+
metadata.prerequisites = extractField('prerequisites');
|
|
81
|
+
metadata.next = extractField('next');
|
|
82
|
+
return metadata;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Cleans a string from WikiLink brackets [[ ]] and whitespace.
|
|
86
|
+
* 清除 WikiLink 括号 [[ ]] 和空格。
|
|
87
|
+
*/
|
|
88
|
+
static cleanLink(raw) {
|
|
89
|
+
let text = raw.trim();
|
|
90
|
+
// Remove [[ ]]
|
|
91
|
+
const linkMatch = text.match(/^\[\[(.*?)(?:\|.*?)?\]\]$/);
|
|
92
|
+
if (linkMatch) {
|
|
93
|
+
return linkMatch[1].trim();
|
|
94
|
+
}
|
|
95
|
+
// Remove quotes if present
|
|
96
|
+
text = text.replace(/^["']|["']$/g, '');
|
|
97
|
+
return text;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Extracts tags from YAML frontmatter and WikiLinks [[tag]].
|
|
101
|
+
* 从 YAML frontmatter 和 WikiLinks [[tag]] 中提取标签。
|
|
102
|
+
* @deprecated Use parse() for metadata. This method mixes concepts.
|
|
103
|
+
*/
|
|
104
|
+
static extractTags(content) {
|
|
105
|
+
const tags = new Set();
|
|
106
|
+
// Use new parser for YAML tags
|
|
107
|
+
const parsed = this.parse(content);
|
|
108
|
+
parsed.tags.forEach(t => tags.add(t));
|
|
109
|
+
// 2. Scan for [[WikiLinks]] in the entire content
|
|
110
|
+
// Regex to capture [[Tag]] or [[Tag|Label]]
|
|
111
|
+
const wikiLinkRegex = /\[\[(.*?)(?:\|.*?)?\]\]/g;
|
|
112
|
+
let wikiMatch;
|
|
113
|
+
while ((wikiMatch = wikiLinkRegex.exec(content)) !== null) {
|
|
114
|
+
if (wikiMatch[1]) {
|
|
115
|
+
tags.add(wikiMatch[1].trim());
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return Array.from(tags);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
exports.FrontmatterParser = FrontmatterParser;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.levenshtein = levenshtein;
|
|
4
|
+
exports.isSimilar = isSimilar;
|
|
5
|
+
exports.checkMatch = checkMatch;
|
|
6
|
+
/**
|
|
7
|
+
* Calculates the Levenshtein distance between two strings.
|
|
8
|
+
* 计算两个字符串之间的 Levenshtein 距离。
|
|
9
|
+
*/
|
|
10
|
+
function levenshtein(a, b) {
|
|
11
|
+
const matrix = [];
|
|
12
|
+
// Increment along the first column of each row
|
|
13
|
+
for (let i = 0; i <= b.length; i++) {
|
|
14
|
+
matrix[i] = [i];
|
|
15
|
+
}
|
|
16
|
+
// Increment each column in the first row
|
|
17
|
+
for (let j = 0; j <= a.length; j++) {
|
|
18
|
+
matrix[0][j] = j;
|
|
19
|
+
}
|
|
20
|
+
// Fill in the rest of the matrix
|
|
21
|
+
for (let i = 1; i <= b.length; i++) {
|
|
22
|
+
for (let j = 1; j <= a.length; j++) {
|
|
23
|
+
if (b.charAt(i - 1) == a.charAt(j - 1)) {
|
|
24
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
|
|
28
|
+
Math.min(matrix[i][j - 1] + 1, // insertion
|
|
29
|
+
matrix[i - 1][j] + 1 // deletion
|
|
30
|
+
));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return matrix[b.length][a.length];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Checks if two strings are similar based on a threshold.
|
|
38
|
+
* 基于阈值检查两个字符串是否相似。
|
|
39
|
+
* @param a String A
|
|
40
|
+
* @param b String B
|
|
41
|
+
* @param threshold Max distance allowed (default 2)
|
|
42
|
+
*/
|
|
43
|
+
function isSimilar(a, b, threshold = 2) {
|
|
44
|
+
if (Math.abs(a.length - b.length) > threshold)
|
|
45
|
+
return false;
|
|
46
|
+
return levenshtein(a.toLowerCase(), b.toLowerCase()) <= threshold;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Checks if the content contains the term based on the provided strategy.
|
|
50
|
+
* 根据提供的策略检查内容是否包含术语。
|
|
51
|
+
* @param content The text content to search within
|
|
52
|
+
* @param term The term to search for
|
|
53
|
+
* @param strategy 'exact-phrase' or 'fuzzy'
|
|
54
|
+
*/
|
|
55
|
+
function checkMatch(content, term, strategy = 'exact-phrase') {
|
|
56
|
+
if (strategy === 'exact-phrase') {
|
|
57
|
+
const escapedTerm = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
58
|
+
const regex = new RegExp(`\\b${escapedTerm}\\b`, 'i');
|
|
59
|
+
return regex.test(content);
|
|
60
|
+
}
|
|
61
|
+
else if (strategy === 'fuzzy') {
|
|
62
|
+
// Simple inclusion as per original logic
|
|
63
|
+
return content.toLowerCase().includes(term.toLowerCase());
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const worker_threads_1 = require("worker_threads");
|
|
4
|
+
const stringUtils_1 = require("../utils/stringUtils");
|
|
5
|
+
const { filesChunk, targetIds, strategy, exclusionList } = worker_threads_1.workerData;
|
|
6
|
+
const results = [];
|
|
7
|
+
filesChunk.forEach(sourceFile => {
|
|
8
|
+
const sourceId = sourceFile.filename;
|
|
9
|
+
const content = sourceFile.content;
|
|
10
|
+
targetIds.forEach(targetId => {
|
|
11
|
+
if (sourceId === targetId)
|
|
12
|
+
return;
|
|
13
|
+
if (exclusionList.includes(targetId))
|
|
14
|
+
return;
|
|
15
|
+
if ((0, stringUtils_1.checkMatch)(content, targetId, strategy)) {
|
|
16
|
+
results.push({ source: sourceId, target: targetId });
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
if (worker_threads_1.parentPort) {
|
|
21
|
+
worker_threads_1.parentPort.postMessage(results);
|
|
22
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Graph = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Directed Graph implementation for managing notes and dependencies.
|
|
6
|
+
* 用于管理笔记和依赖关系的有向图实现。
|
|
7
|
+
*/
|
|
8
|
+
class Graph {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.nodes = new Map();
|
|
11
|
+
this.adjacencyList = new Map();
|
|
12
|
+
this.reverseAdjacencyList = new Map();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Adds a node to the graph.
|
|
16
|
+
* 向图中添加一个节点。
|
|
17
|
+
* @param node The node to add | 要添加的节点
|
|
18
|
+
*/
|
|
19
|
+
addNode(node) {
|
|
20
|
+
if (!this.nodes.has(node.id)) {
|
|
21
|
+
this.nodes.set(node.id, { ...node, inDegree: 0, outDegree: 0 });
|
|
22
|
+
this.adjacencyList.set(node.id, []);
|
|
23
|
+
this.reverseAdjacencyList.set(node.id, []);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Retrieves a node by its ID.
|
|
28
|
+
* 通过 ID 获取节点。
|
|
29
|
+
* @param id The node ID | 节点 ID
|
|
30
|
+
* @returns The node or undefined if not found | 节点,如果未找到则返回 undefined
|
|
31
|
+
*/
|
|
32
|
+
getNode(id) {
|
|
33
|
+
return this.nodes.get(id);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Checks if a node exists in the graph.
|
|
37
|
+
* 检查图中是否存在该节点。
|
|
38
|
+
* @param id The node ID | 节点 ID
|
|
39
|
+
*/
|
|
40
|
+
hasNode(id) {
|
|
41
|
+
return this.nodes.has(id);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Adds a directed edge between two nodes.
|
|
45
|
+
* 在两个节点之间添加有向边。
|
|
46
|
+
* @param source Source node ID | 源节点 ID
|
|
47
|
+
* @param target Target node ID | 目标节点 ID
|
|
48
|
+
* @param type Relationship type | 关系类型
|
|
49
|
+
* @param weight Edge weight (confidence) | 边权重 (置信度)
|
|
50
|
+
*/
|
|
51
|
+
addEdge(source, target, type = 'dependency', weight = 1) {
|
|
52
|
+
if (!this.nodes.has(source)) {
|
|
53
|
+
this.addNode({ id: source, label: source, inDegree: 0, outDegree: 0 });
|
|
54
|
+
}
|
|
55
|
+
if (!this.nodes.has(target)) {
|
|
56
|
+
this.addNode({ id: target, label: target, inDegree: 0, outDegree: 0 });
|
|
57
|
+
}
|
|
58
|
+
const edge = { source, target, type, weight };
|
|
59
|
+
// Add to adjacency list (outgoing)
|
|
60
|
+
const outgoing = this.adjacencyList.get(source) || [];
|
|
61
|
+
// Prevent duplicate edges
|
|
62
|
+
if (!outgoing.some(e => e.target === target && e.type === type)) {
|
|
63
|
+
outgoing.push(edge);
|
|
64
|
+
this.adjacencyList.set(source, outgoing);
|
|
65
|
+
// Update out-degree
|
|
66
|
+
const sourceNode = this.nodes.get(source);
|
|
67
|
+
sourceNode.outDegree++;
|
|
68
|
+
}
|
|
69
|
+
// Add to reverse adjacency list (incoming)
|
|
70
|
+
const incoming = this.reverseAdjacencyList.get(target) || [];
|
|
71
|
+
if (!incoming.some(e => e.source === source && e.type === type)) {
|
|
72
|
+
incoming.push(edge);
|
|
73
|
+
this.reverseAdjacencyList.set(target, incoming);
|
|
74
|
+
// Update in-degree
|
|
75
|
+
const targetNode = this.nodes.get(target);
|
|
76
|
+
targetNode.inDegree++;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Gets all outgoing edges from a node.
|
|
81
|
+
* 获取节点的所有出边。
|
|
82
|
+
* @param id Node ID | 节点 ID
|
|
83
|
+
*/
|
|
84
|
+
getOutgoingEdges(id) {
|
|
85
|
+
return this.adjacencyList.get(id) || [];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Gets all outgoing neighbor IDs for a node.
|
|
89
|
+
* 获取节点的所有出度邻居 ID。
|
|
90
|
+
* @param id Node ID | 节点 ID
|
|
91
|
+
*/
|
|
92
|
+
getNeighbors(id) {
|
|
93
|
+
return (this.adjacencyList.get(id) || []).map(edge => edge.target);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Gets all nodes in the graph.
|
|
97
|
+
* 获取图中的所有节点。
|
|
98
|
+
*/
|
|
99
|
+
getNodes() {
|
|
100
|
+
return Array.from(this.nodes.values());
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Gets all incoming edges to a node.
|
|
104
|
+
* 获取节点的所有入边。
|
|
105
|
+
* @param id Node ID | 节点 ID
|
|
106
|
+
*/
|
|
107
|
+
getIncomingEdges(id) {
|
|
108
|
+
return this.reverseAdjacencyList.get(id) || [];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Returns the graph data in a serializable format.
|
|
112
|
+
* 以可序列化的格式返回图数据。
|
|
113
|
+
*/
|
|
114
|
+
toJSON() {
|
|
115
|
+
return {
|
|
116
|
+
nodes: Array.from(this.nodes.values()),
|
|
117
|
+
edges: Array.from(this.adjacencyList.values()).flat()
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
exports.Graph = Graph;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const Graph_1 = require("./Graph");
|
|
4
|
+
describe('Graph Core', () => {
|
|
5
|
+
let graph;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
graph = new Graph_1.Graph();
|
|
8
|
+
});
|
|
9
|
+
test('should add a node correctly', () => {
|
|
10
|
+
const node = { id: 'A', label: 'Note A', inDegree: 0, outDegree: 0 };
|
|
11
|
+
graph.addNode(node);
|
|
12
|
+
expect(graph.hasNode('A')).toBe(true);
|
|
13
|
+
expect(graph.getNode('A')).toEqual(expect.objectContaining({ id: 'A' }));
|
|
14
|
+
});
|
|
15
|
+
test('should add an edge and update degrees', () => {
|
|
16
|
+
graph.addEdge('A', 'B');
|
|
17
|
+
expect(graph.hasNode('A')).toBe(true);
|
|
18
|
+
expect(graph.hasNode('B')).toBe(true);
|
|
19
|
+
const nodeA = graph.getNode('A');
|
|
20
|
+
const nodeB = graph.getNode('B');
|
|
21
|
+
expect(nodeA?.outDegree).toBe(1);
|
|
22
|
+
expect(nodeB?.inDegree).toBe(1);
|
|
23
|
+
const outgoing = graph.getOutgoingEdges('A');
|
|
24
|
+
expect(outgoing).toHaveLength(1);
|
|
25
|
+
expect(outgoing[0].target).toBe('B');
|
|
26
|
+
const incoming = graph.getIncomingEdges('B');
|
|
27
|
+
expect(incoming).toHaveLength(1);
|
|
28
|
+
expect(incoming[0].source).toBe('A');
|
|
29
|
+
});
|
|
30
|
+
test('should not add duplicate edges', () => {
|
|
31
|
+
graph.addEdge('A', 'B');
|
|
32
|
+
graph.addEdge('A', 'B'); // Duplicate
|
|
33
|
+
const nodeA = graph.getNode('A');
|
|
34
|
+
expect(nodeA?.outDegree).toBe(1);
|
|
35
|
+
expect(graph.getOutgoingEdges('A')).toHaveLength(1);
|
|
36
|
+
});
|
|
37
|
+
});
|