doc2vec 1.0.2 → 1.0.4
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/dist/doc2vec.js +1 -1
- package/doc2vec.ts +1 -1
- package/package.json +1 -1
- package/Dockerfile.old +0 -19
- package/config copy.yaml +0 -182
- package/config-github.yaml +0 -15
- package/config.yaml.new +0 -253
- package/config.yaml.ok +0 -164
- package/config.yaml.solo +0 -251
- package/dist/solo-reference-architectures/Gloo_Edge/ALB_Last_Mile_Encryption/data/acm-waf-cdk/lib/acm-cdk-stack.js +0 -20
- package/dist/solo-reference-architectures/Gloo_Edge/ALB_Last_Mile_Encryption/data/acm-waf-cdk/lib/route53-cdk-stack.js +0 -33
- package/dist/solo-reference-architectures/Gloo_Edge/ALB_Last_Mile_Encryption/data/acm-waf-cdk/lib/waf-cdk-stack.js +0 -66
- package/dist/solo-reference-architectures/Gloo_Edge/ALB_Last_Mile_Encryption/data/acm-waf-cdk/test/s3-cloudfront-cdk.test.js +0 -16
- package/dist/solo-reference-architectures/Gloo_Edge/Cloudfront_NLB/data/s3-cloudfront-cdk/lib/acm-cdk-stack.js +0 -21
- package/dist/solo-reference-architectures/Gloo_Edge/Cloudfront_NLB/data/s3-cloudfront-cdk/lib/s3-cloudfront-cdk-stack.js +0 -95
- package/dist/solo-reference-architectures/Gloo_Edge/Cloudfront_NLB/data/s3-cloudfront-cdk/test/s3-cloudfront-cdk.test.js +0 -16
- package/dist/solo-reference-architectures/Gloo_Gateway/aws-nlb-tls-offloading/data/acm-cdk/bin/acm-cdk.js +0 -55
- package/dist/solo-reference-architectures/Gloo_Gateway/aws-nlb-tls-offloading/data/acm-cdk/lib/acm-cdk-stack.js +0 -20
- package/dist/solo-reference-architectures/Gloo_Gateway/aws-nlb-tls-offloading/data/acm-cdk/lib/route53-cdk-stack.js +0 -33
- package/dist/solo-reference-architectures/Gloo_Gateway/aws-nlb-tls-offloading/data/acm-cdk/test/s3-cloudfront-cdk.test.js +0 -16
- package/doc2vec.js +0 -1805
- package/doc2vec.ts.new +0 -1583
- package/doc2vec.ts.ok +0 -1730
- package/istio-issues.db +0 -0
- package/logger.js +0 -260
- package/tmp/README.md.old +0 -1
- package/tmp.db +0 -0
package/doc2vec.js
DELETED
|
@@ -1,1805 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ts-node
|
|
2
|
-
"use strict";
|
|
3
|
-
var __assign = (this && this.__assign) || function () {
|
|
4
|
-
__assign = Object.assign || function(t) {
|
|
5
|
-
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
6
|
-
s = arguments[i];
|
|
7
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
8
|
-
t[p] = s[p];
|
|
9
|
-
}
|
|
10
|
-
return t;
|
|
11
|
-
};
|
|
12
|
-
return __assign.apply(this, arguments);
|
|
13
|
-
};
|
|
14
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
15
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
16
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
17
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
18
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
19
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
20
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
21
|
-
});
|
|
22
|
-
};
|
|
23
|
-
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
24
|
-
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
25
|
-
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
26
|
-
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
27
|
-
function step(op) {
|
|
28
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
29
|
-
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
30
|
-
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
31
|
-
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
32
|
-
switch (op[0]) {
|
|
33
|
-
case 0: case 1: t = op; break;
|
|
34
|
-
case 4: _.label++; return { value: op[1], done: false };
|
|
35
|
-
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
36
|
-
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
37
|
-
default:
|
|
38
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
39
|
-
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
40
|
-
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
41
|
-
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
42
|
-
if (t[2]) _.ops.pop();
|
|
43
|
-
_.trys.pop(); continue;
|
|
44
|
-
}
|
|
45
|
-
op = body.call(thisArg, _);
|
|
46
|
-
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
47
|
-
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
51
|
-
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
52
|
-
if (ar || !(i in from)) {
|
|
53
|
-
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
54
|
-
ar[i] = from[i];
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return to.concat(ar || Array.prototype.slice.call(from));
|
|
58
|
-
};
|
|
59
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
60
|
-
var readability_1 = require("@mozilla/readability");
|
|
61
|
-
var axios_1 = require("axios");
|
|
62
|
-
var cheerio_1 = require("cheerio");
|
|
63
|
-
var crypto_1 = require("crypto");
|
|
64
|
-
var jsdom_1 = require("jsdom");
|
|
65
|
-
var puppeteer_1 = require("puppeteer");
|
|
66
|
-
var sanitize_html_1 = require("sanitize-html");
|
|
67
|
-
var turndown_1 = require("turndown");
|
|
68
|
-
var yaml = require("js-yaml");
|
|
69
|
-
var fs = require("fs");
|
|
70
|
-
var path = require("path");
|
|
71
|
-
var better_sqlite3_1 = require("better-sqlite3");
|
|
72
|
-
var openai_1 = require("openai");
|
|
73
|
-
var dotenv = require("dotenv");
|
|
74
|
-
var sqliteVec = require("sqlite-vec");
|
|
75
|
-
var js_client_rest_1 = require("@qdrant/js-client-rest");
|
|
76
|
-
var logger_1 = require("./logger");
|
|
77
|
-
var GITHUB_TOKEN = process.env.GITHUB_PERSONAL_ACCESS_TOKEN;
|
|
78
|
-
dotenv.config();
|
|
79
|
-
var Doc2Vec = /** @class */ (function () {
|
|
80
|
-
function Doc2Vec(configPath) {
|
|
81
|
-
this.logger = new logger_1.Logger('Doc2Vec', {
|
|
82
|
-
level: logger_1.LogLevel.DEBUG,
|
|
83
|
-
useTimestamp: true,
|
|
84
|
-
useColor: true,
|
|
85
|
-
prettyPrint: true
|
|
86
|
-
});
|
|
87
|
-
this.logger.info('Initializing Doc2Vec');
|
|
88
|
-
this.config = this.loadConfig(configPath);
|
|
89
|
-
this.openai = new openai_1.OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
|
90
|
-
this.turndownService = new turndown_1.default({
|
|
91
|
-
codeBlockStyle: 'fenced',
|
|
92
|
-
headingStyle: 'atx'
|
|
93
|
-
});
|
|
94
|
-
this.setupTurndownRules();
|
|
95
|
-
}
|
|
96
|
-
Doc2Vec.prototype.loadConfig = function (configPath) {
|
|
97
|
-
try {
|
|
98
|
-
var logger = this.logger.child('config');
|
|
99
|
-
logger.info("Loading configuration from ".concat(configPath));
|
|
100
|
-
var configFile = fs.readFileSync(configPath, 'utf8');
|
|
101
|
-
var config = yaml.load(configFile);
|
|
102
|
-
var typedConfig = config;
|
|
103
|
-
logger.info("Configuration loaded successfully, found ".concat(typedConfig.sources.length, " sources"));
|
|
104
|
-
return typedConfig;
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
this.logger.error("Failed to load or parse config file at ".concat(configPath, ":"), error);
|
|
108
|
-
process.exit(1);
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
Doc2Vec.prototype.setupTurndownRules = function () {
|
|
112
|
-
var logger = this.logger.child('markdown');
|
|
113
|
-
logger.debug('Setting up Turndown rules for markdown conversion');
|
|
114
|
-
this.turndownService.addRule('codeBlocks', {
|
|
115
|
-
filter: function (node) { return node.nodeName === 'PRE'; },
|
|
116
|
-
replacement: function (content, node) {
|
|
117
|
-
var _a;
|
|
118
|
-
var htmlNode = node;
|
|
119
|
-
var code = htmlNode.querySelector('code');
|
|
120
|
-
var codeContent;
|
|
121
|
-
if (code) {
|
|
122
|
-
codeContent = code.textContent || '';
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
codeContent = htmlNode.textContent || '';
|
|
126
|
-
}
|
|
127
|
-
var lines = codeContent.split('\n');
|
|
128
|
-
var minIndent = Infinity;
|
|
129
|
-
for (var _i = 0, lines_1 = lines; _i < lines_1.length; _i++) {
|
|
130
|
-
var line = lines_1[_i];
|
|
131
|
-
if (line.trim() === '')
|
|
132
|
-
continue;
|
|
133
|
-
var leadingWhitespace = ((_a = line.match(/^\s*/)) === null || _a === void 0 ? void 0 : _a[0]) || '';
|
|
134
|
-
minIndent = Math.min(minIndent, leadingWhitespace.length);
|
|
135
|
-
}
|
|
136
|
-
var cleanedLines = lines.map(function (line) {
|
|
137
|
-
return line.substring(minIndent);
|
|
138
|
-
});
|
|
139
|
-
var cleanContent = cleanedLines.join('\n');
|
|
140
|
-
cleanContent = cleanContent.replace(/^\s+|\s+$/g, '');
|
|
141
|
-
cleanContent = cleanContent.replace(/\n{2,}/g, '\n');
|
|
142
|
-
return "\n```\n".concat(cleanContent, "\n```\n");
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
this.turndownService.addRule('tableCell', {
|
|
146
|
-
filter: ['th', 'td'],
|
|
147
|
-
replacement: function (content, node) {
|
|
148
|
-
var htmlNode = node;
|
|
149
|
-
var cellContent = '';
|
|
150
|
-
if (htmlNode.querySelector('p')) {
|
|
151
|
-
cellContent = Array.from(htmlNode.querySelectorAll('p'))
|
|
152
|
-
.map(function (p) { return p.textContent || ''; })
|
|
153
|
-
.join(' ')
|
|
154
|
-
.trim();
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
cellContent = content.trim();
|
|
158
|
-
}
|
|
159
|
-
return " ".concat(cellContent.replace(/\|/g, '\\|'), " |");
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
this.turndownService.addRule('tableRow', {
|
|
163
|
-
filter: 'tr',
|
|
164
|
-
replacement: function (content, node) {
|
|
165
|
-
var _a;
|
|
166
|
-
var htmlNode = node;
|
|
167
|
-
var cells = Array.from(htmlNode.cells);
|
|
168
|
-
var isHeader = ((_a = htmlNode.parentNode) === null || _a === void 0 ? void 0 : _a.nodeName) === 'THEAD';
|
|
169
|
-
var output = '|' + content.trimEnd();
|
|
170
|
-
if (isHeader) {
|
|
171
|
-
var separator = cells.map(function () { return '---'; }).join(' | ');
|
|
172
|
-
output += '\n|' + separator + '|';
|
|
173
|
-
}
|
|
174
|
-
if (!isHeader || !htmlNode.nextElementSibling) {
|
|
175
|
-
output += '\n';
|
|
176
|
-
}
|
|
177
|
-
return output;
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
this.turndownService.addRule('table', {
|
|
181
|
-
filter: 'table',
|
|
182
|
-
replacement: function (content) {
|
|
183
|
-
return '\n' + content.replace(/\n+/g, '\n').trim() + '\n';
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
this.turndownService.addRule('preserveTableWhitespace', {
|
|
187
|
-
filter: function (node) {
|
|
188
|
-
var _a;
|
|
189
|
-
return ((node.nodeName === 'TD' || node.nodeName === 'TH') &&
|
|
190
|
-
(((_a = node.textContent) === null || _a === void 0 ? void 0 : _a.trim().length) === 0));
|
|
191
|
-
},
|
|
192
|
-
replacement: function () {
|
|
193
|
-
return ' |';
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
logger.debug('Turndown rules setup complete');
|
|
197
|
-
};
|
|
198
|
-
Doc2Vec.prototype.run = function () {
|
|
199
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
200
|
-
var _i, _a, sourceConfig, sourceLogger;
|
|
201
|
-
return __generator(this, function (_b) {
|
|
202
|
-
switch (_b.label) {
|
|
203
|
-
case 0:
|
|
204
|
-
this.logger.section('PROCESSING SOURCES');
|
|
205
|
-
_i = 0, _a = this.config.sources;
|
|
206
|
-
_b.label = 1;
|
|
207
|
-
case 1:
|
|
208
|
-
if (!(_i < _a.length)) return [3 /*break*/, 9];
|
|
209
|
-
sourceConfig = _a[_i];
|
|
210
|
-
sourceLogger = this.logger.child("source:".concat(sourceConfig.product_name));
|
|
211
|
-
sourceLogger.info("Processing ".concat(sourceConfig.type, " source for ").concat(sourceConfig.product_name, "@").concat(sourceConfig.version));
|
|
212
|
-
if (!(sourceConfig.type === 'github')) return [3 /*break*/, 3];
|
|
213
|
-
return [4 /*yield*/, this.processGithubRepo(sourceConfig, sourceLogger)];
|
|
214
|
-
case 2:
|
|
215
|
-
_b.sent();
|
|
216
|
-
return [3 /*break*/, 8];
|
|
217
|
-
case 3:
|
|
218
|
-
if (!(sourceConfig.type === 'website')) return [3 /*break*/, 5];
|
|
219
|
-
return [4 /*yield*/, this.processWebsite(sourceConfig, sourceLogger)];
|
|
220
|
-
case 4:
|
|
221
|
-
_b.sent();
|
|
222
|
-
return [3 /*break*/, 8];
|
|
223
|
-
case 5:
|
|
224
|
-
if (!(sourceConfig.type === 'local_directory')) return [3 /*break*/, 7];
|
|
225
|
-
return [4 /*yield*/, this.processLocalDirectory(sourceConfig, sourceLogger)];
|
|
226
|
-
case 6:
|
|
227
|
-
_b.sent();
|
|
228
|
-
return [3 /*break*/, 8];
|
|
229
|
-
case 7:
|
|
230
|
-
sourceLogger.error("Unknown source type: ".concat(sourceConfig.type));
|
|
231
|
-
_b.label = 8;
|
|
232
|
-
case 8:
|
|
233
|
-
_i++;
|
|
234
|
-
return [3 /*break*/, 1];
|
|
235
|
-
case 9:
|
|
236
|
-
this.logger.section('PROCESSING COMPLETE');
|
|
237
|
-
return [2 /*return*/];
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
};
|
|
242
|
-
Doc2Vec.prototype.getUrlPrefix = function (url) {
|
|
243
|
-
try {
|
|
244
|
-
var parsedUrl = new URL(url);
|
|
245
|
-
return parsedUrl.origin + parsedUrl.pathname;
|
|
246
|
-
}
|
|
247
|
-
catch (error) {
|
|
248
|
-
return url;
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
Doc2Vec.prototype.generateMetadataUUID = function (repo) {
|
|
252
|
-
// Simple deterministic approach - hash the repo name and convert to UUID format
|
|
253
|
-
var hash = crypto_1.default.createHash('md5').update("metadata_".concat(repo)).digest('hex');
|
|
254
|
-
// Format as UUID with version bits set correctly (version 4)
|
|
255
|
-
return "".concat(hash.substr(0, 8), "-").concat(hash.substr(8, 4), "-4").concat(hash.substr(13, 3), "-").concat(hash.substr(16, 4), "-").concat(hash.substr(20, 12));
|
|
256
|
-
};
|
|
257
|
-
Doc2Vec.prototype.initDatabaseMetadata = function (dbConnection) {
|
|
258
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
259
|
-
var logger, db;
|
|
260
|
-
return __generator(this, function (_a) {
|
|
261
|
-
logger = this.logger.child('metadata');
|
|
262
|
-
if (dbConnection.type === 'sqlite') {
|
|
263
|
-
db = dbConnection.db;
|
|
264
|
-
logger.debug('Creating metadata table if it doesn\'t exist');
|
|
265
|
-
db.exec("\n CREATE TABLE IF NOT EXISTS vec_metadata (\n key TEXT PRIMARY KEY,\n value TEXT\n );\n ");
|
|
266
|
-
logger.info('SQLite metadata table initialized');
|
|
267
|
-
}
|
|
268
|
-
else if (dbConnection.type === 'qdrant') {
|
|
269
|
-
// For Qdrant, we'll use the same collection but verify it exists
|
|
270
|
-
logger.info("Using existing Qdrant collection for metadata: ".concat(dbConnection.collectionName));
|
|
271
|
-
// Nothing special to initialize as we'll use the same collection
|
|
272
|
-
}
|
|
273
|
-
return [2 /*return*/];
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
};
|
|
277
|
-
Doc2Vec.prototype.getLastRunDate = function (dbConnection, repo, defaultDate) {
|
|
278
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
279
|
-
var logger, metadataKey, stmt, result, metadataUUID, response, lastRunDate, error_1, error_2;
|
|
280
|
-
var _a;
|
|
281
|
-
return __generator(this, function (_b) {
|
|
282
|
-
switch (_b.label) {
|
|
283
|
-
case 0:
|
|
284
|
-
logger = this.logger.child('metadata');
|
|
285
|
-
metadataKey = "last_run_".concat(repo.replace('/', '_'));
|
|
286
|
-
_b.label = 1;
|
|
287
|
-
case 1:
|
|
288
|
-
_b.trys.push([1, 7, , 8]);
|
|
289
|
-
if (!(dbConnection.type === 'sqlite')) return [3 /*break*/, 2];
|
|
290
|
-
stmt = dbConnection.db.prepare('SELECT value FROM vec_metadata WHERE key = ?');
|
|
291
|
-
result = stmt.get(metadataKey);
|
|
292
|
-
if (result) {
|
|
293
|
-
logger.info("Retrieved last run date for ".concat(repo, ": ").concat(result.value));
|
|
294
|
-
return [2 /*return*/, result.value];
|
|
295
|
-
}
|
|
296
|
-
return [3 /*break*/, 6];
|
|
297
|
-
case 2:
|
|
298
|
-
if (!(dbConnection.type === 'qdrant')) return [3 /*break*/, 6];
|
|
299
|
-
metadataUUID = this.generateMetadataUUID(repo);
|
|
300
|
-
logger.debug("Looking up metadata with UUID: ".concat(metadataUUID));
|
|
301
|
-
_b.label = 3;
|
|
302
|
-
case 3:
|
|
303
|
-
_b.trys.push([3, 5, , 6]);
|
|
304
|
-
return [4 /*yield*/, dbConnection.client.retrieve(dbConnection.collectionName, {
|
|
305
|
-
ids: [metadataUUID],
|
|
306
|
-
with_payload: true,
|
|
307
|
-
with_vector: false
|
|
308
|
-
})];
|
|
309
|
-
case 4:
|
|
310
|
-
response = _b.sent();
|
|
311
|
-
if (response.length > 0 && ((_a = response[0].payload) === null || _a === void 0 ? void 0 : _a.metadata_value)) {
|
|
312
|
-
lastRunDate = response[0].payload.metadata_value;
|
|
313
|
-
logger.info("Retrieved last run date for ".concat(repo, ": ").concat(lastRunDate));
|
|
314
|
-
return [2 /*return*/, lastRunDate];
|
|
315
|
-
}
|
|
316
|
-
return [3 /*break*/, 6];
|
|
317
|
-
case 5:
|
|
318
|
-
error_1 = _b.sent();
|
|
319
|
-
logger.warn("Failed to retrieve metadata for ".concat(repo, ":"), error_1);
|
|
320
|
-
return [3 /*break*/, 6];
|
|
321
|
-
case 6: return [3 /*break*/, 8];
|
|
322
|
-
case 7:
|
|
323
|
-
error_2 = _b.sent();
|
|
324
|
-
logger.warn("Error retrieving last run date:", error_2);
|
|
325
|
-
return [3 /*break*/, 8];
|
|
326
|
-
case 8:
|
|
327
|
-
logger.info("No saved run date found for ".concat(repo, ", using default: ").concat(defaultDate));
|
|
328
|
-
return [2 /*return*/, defaultDate];
|
|
329
|
-
}
|
|
330
|
-
});
|
|
331
|
-
});
|
|
332
|
-
};
|
|
333
|
-
Doc2Vec.prototype.updateLastRunDate = function (dbConnection, repo) {
|
|
334
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
335
|
-
var logger, now, metadataKey, stmt, metadataUUID, metadataKey, dummyEmbeddingSize, dummyEmbedding, metadataPoint, error_3;
|
|
336
|
-
return __generator(this, function (_a) {
|
|
337
|
-
switch (_a.label) {
|
|
338
|
-
case 0:
|
|
339
|
-
logger = this.logger.child('metadata');
|
|
340
|
-
now = new Date().toISOString();
|
|
341
|
-
_a.label = 1;
|
|
342
|
-
case 1:
|
|
343
|
-
_a.trys.push([1, 5, , 6]);
|
|
344
|
-
if (!(dbConnection.type === 'sqlite')) return [3 /*break*/, 2];
|
|
345
|
-
metadataKey = "last_run_".concat(repo.replace('/', '_'));
|
|
346
|
-
stmt = dbConnection.db.prepare("\n INSERT INTO vec_metadata (key, value) VALUES (?, ?)\n ON CONFLICT(key) DO UPDATE SET value = excluded.value\n ");
|
|
347
|
-
stmt.run(metadataKey, now);
|
|
348
|
-
logger.info("Updated last run date for ".concat(repo, " to ").concat(now));
|
|
349
|
-
return [3 /*break*/, 4];
|
|
350
|
-
case 2:
|
|
351
|
-
if (!(dbConnection.type === 'qdrant')) return [3 /*break*/, 4];
|
|
352
|
-
metadataUUID = this.generateMetadataUUID(repo);
|
|
353
|
-
metadataKey = "last_run_".concat(repo.replace('/', '_'));
|
|
354
|
-
logger.debug("Using UUID: ".concat(metadataUUID, " for metadata"));
|
|
355
|
-
dummyEmbeddingSize = 3072;
|
|
356
|
-
dummyEmbedding = new Array(dummyEmbeddingSize).fill(0);
|
|
357
|
-
metadataPoint = {
|
|
358
|
-
id: metadataUUID,
|
|
359
|
-
vector: dummyEmbedding,
|
|
360
|
-
payload: {
|
|
361
|
-
metadata_key: metadataKey,
|
|
362
|
-
metadata_value: now,
|
|
363
|
-
is_metadata: true, // Flag to identify metadata points
|
|
364
|
-
content: "Metadata: Last run date for ".concat(repo),
|
|
365
|
-
product_name: 'system',
|
|
366
|
-
version: 'metadata',
|
|
367
|
-
url: 'metadata://' + repo
|
|
368
|
-
}
|
|
369
|
-
};
|
|
370
|
-
return [4 /*yield*/, dbConnection.client.upsert(dbConnection.collectionName, {
|
|
371
|
-
wait: true,
|
|
372
|
-
points: [metadataPoint]
|
|
373
|
-
})];
|
|
374
|
-
case 3:
|
|
375
|
-
_a.sent();
|
|
376
|
-
logger.info("Updated last run date for ".concat(repo, " to ").concat(now));
|
|
377
|
-
_a.label = 4;
|
|
378
|
-
case 4: return [3 /*break*/, 6];
|
|
379
|
-
case 5:
|
|
380
|
-
error_3 = _a.sent();
|
|
381
|
-
logger.error("Failed to update last run date for ".concat(repo, ":"), error_3);
|
|
382
|
-
return [3 /*break*/, 6];
|
|
383
|
-
case 6: return [2 /*return*/];
|
|
384
|
-
}
|
|
385
|
-
});
|
|
386
|
-
});
|
|
387
|
-
};
|
|
388
|
-
Doc2Vec.prototype.fetchAndProcessGitHubIssues = function (repo, sourceConfig, dbConnection, logger) {
|
|
389
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
390
|
-
var _a, owner, repoName, GITHUB_API_URL, startDate, lastRunDate, fetchWithRetry, fetchAllIssues, fetchIssueComments, generateMarkdownForIssue, processIssue, issues, i;
|
|
391
|
-
var _this = this;
|
|
392
|
-
return __generator(this, function (_b) {
|
|
393
|
-
switch (_b.label) {
|
|
394
|
-
case 0:
|
|
395
|
-
_a = repo.split('/'), owner = _a[0], repoName = _a[1];
|
|
396
|
-
GITHUB_API_URL = "https://api.github.com/repos/".concat(owner, "/").concat(repoName, "/issues");
|
|
397
|
-
// Initialize metadata storage if needed
|
|
398
|
-
return [4 /*yield*/, this.initDatabaseMetadata(dbConnection)];
|
|
399
|
-
case 1:
|
|
400
|
-
// Initialize metadata storage if needed
|
|
401
|
-
_b.sent();
|
|
402
|
-
startDate = sourceConfig.start_date || '2025-01-01';
|
|
403
|
-
return [4 /*yield*/, this.getLastRunDate(dbConnection, repo, "".concat(startDate, "T00:00:00Z"))];
|
|
404
|
-
case 2:
|
|
405
|
-
lastRunDate = _b.sent();
|
|
406
|
-
fetchWithRetry = function (url_1) {
|
|
407
|
-
var args_1 = [];
|
|
408
|
-
for (var _i = 1; _i < arguments.length; _i++) {
|
|
409
|
-
args_1[_i - 1] = arguments[_i];
|
|
410
|
-
}
|
|
411
|
-
return __awaiter(_this, __spreadArray([url_1], args_1, true), void 0, function (url, params, retries, delay) {
|
|
412
|
-
var _loop_1, attempt, state_1;
|
|
413
|
-
if (params === void 0) { params = {}; }
|
|
414
|
-
if (retries === void 0) { retries = 5; }
|
|
415
|
-
if (delay === void 0) { delay = 5000; }
|
|
416
|
-
return __generator(this, function (_a) {
|
|
417
|
-
switch (_a.label) {
|
|
418
|
-
case 0:
|
|
419
|
-
_loop_1 = function (attempt) {
|
|
420
|
-
var response, error_4, resetTime, currentTime, waitTime_1;
|
|
421
|
-
return __generator(this, function (_b) {
|
|
422
|
-
switch (_b.label) {
|
|
423
|
-
case 0:
|
|
424
|
-
_b.trys.push([0, 2, , 6]);
|
|
425
|
-
return [4 /*yield*/, axios_1.default.get(url, {
|
|
426
|
-
headers: {
|
|
427
|
-
Authorization: "token ".concat(GITHUB_TOKEN),
|
|
428
|
-
Accept: 'application/vnd.github.v3+json',
|
|
429
|
-
},
|
|
430
|
-
params: params,
|
|
431
|
-
})];
|
|
432
|
-
case 1:
|
|
433
|
-
response = _b.sent();
|
|
434
|
-
return [2 /*return*/, { value: response.data }];
|
|
435
|
-
case 2:
|
|
436
|
-
error_4 = _b.sent();
|
|
437
|
-
if (!(error_4.response && error_4.response.status === 403)) return [3 /*break*/, 4];
|
|
438
|
-
resetTime = error_4.response.headers['x-ratelimit-reset'];
|
|
439
|
-
currentTime = Math.floor(Date.now() / 1000);
|
|
440
|
-
waitTime_1 = resetTime ? (resetTime - currentTime) * 1000 : delay * 2;
|
|
441
|
-
logger.warn("GitHub rate limit exceeded. Waiting ".concat(waitTime_1 / 1000, "s"));
|
|
442
|
-
return [4 /*yield*/, new Promise(function (res) { return setTimeout(res, waitTime_1); })];
|
|
443
|
-
case 3:
|
|
444
|
-
_b.sent();
|
|
445
|
-
return [3 /*break*/, 5];
|
|
446
|
-
case 4:
|
|
447
|
-
logger.error("GitHub fetch failed: ".concat(error_4.message));
|
|
448
|
-
throw error_4;
|
|
449
|
-
case 5: return [3 /*break*/, 6];
|
|
450
|
-
case 6: return [2 /*return*/];
|
|
451
|
-
}
|
|
452
|
-
});
|
|
453
|
-
};
|
|
454
|
-
attempt = 0;
|
|
455
|
-
_a.label = 1;
|
|
456
|
-
case 1:
|
|
457
|
-
if (!(attempt < retries)) return [3 /*break*/, 4];
|
|
458
|
-
return [5 /*yield**/, _loop_1(attempt)];
|
|
459
|
-
case 2:
|
|
460
|
-
state_1 = _a.sent();
|
|
461
|
-
if (typeof state_1 === "object")
|
|
462
|
-
return [2 /*return*/, state_1.value];
|
|
463
|
-
_a.label = 3;
|
|
464
|
-
case 3:
|
|
465
|
-
attempt++;
|
|
466
|
-
return [3 /*break*/, 1];
|
|
467
|
-
case 4: throw new Error('Max retries reached');
|
|
468
|
-
}
|
|
469
|
-
});
|
|
470
|
-
});
|
|
471
|
-
};
|
|
472
|
-
fetchAllIssues = function (sinceDate) { return __awaiter(_this, void 0, void 0, function () {
|
|
473
|
-
var issues, page, perPage, sinceTimestamp, data, filtered;
|
|
474
|
-
return __generator(this, function (_a) {
|
|
475
|
-
switch (_a.label) {
|
|
476
|
-
case 0:
|
|
477
|
-
issues = [];
|
|
478
|
-
page = 1;
|
|
479
|
-
perPage = 100;
|
|
480
|
-
sinceTimestamp = new Date(sinceDate);
|
|
481
|
-
_a.label = 1;
|
|
482
|
-
case 1:
|
|
483
|
-
if (!true) return [3 /*break*/, 3];
|
|
484
|
-
return [4 /*yield*/, fetchWithRetry(GITHUB_API_URL, {
|
|
485
|
-
per_page: perPage,
|
|
486
|
-
page: page,
|
|
487
|
-
state: 'all',
|
|
488
|
-
since: sinceDate,
|
|
489
|
-
})];
|
|
490
|
-
case 2:
|
|
491
|
-
data = _a.sent();
|
|
492
|
-
if (data.length === 0)
|
|
493
|
-
return [3 /*break*/, 3];
|
|
494
|
-
filtered = data.filter(function (issue) { return new Date(issue.created_at) >= sinceTimestamp; });
|
|
495
|
-
issues = issues.concat(filtered);
|
|
496
|
-
if (filtered.length < data.length)
|
|
497
|
-
return [3 /*break*/, 3];
|
|
498
|
-
page++;
|
|
499
|
-
return [3 /*break*/, 1];
|
|
500
|
-
case 3: return [2 /*return*/, issues];
|
|
501
|
-
}
|
|
502
|
-
});
|
|
503
|
-
}); };
|
|
504
|
-
fetchIssueComments = function (issueNumber) { return __awaiter(_this, void 0, void 0, function () {
|
|
505
|
-
var url;
|
|
506
|
-
return __generator(this, function (_a) {
|
|
507
|
-
switch (_a.label) {
|
|
508
|
-
case 0:
|
|
509
|
-
url = "".concat(GITHUB_API_URL, "/").concat(issueNumber, "/comments");
|
|
510
|
-
return [4 /*yield*/, fetchWithRetry(url)];
|
|
511
|
-
case 1: return [2 /*return*/, _a.sent()];
|
|
512
|
-
}
|
|
513
|
-
});
|
|
514
|
-
}); };
|
|
515
|
-
generateMarkdownForIssue = function (issue) { return __awaiter(_this, void 0, void 0, function () {
|
|
516
|
-
var comments, md, _i, comments_1, c;
|
|
517
|
-
return __generator(this, function (_a) {
|
|
518
|
-
switch (_a.label) {
|
|
519
|
-
case 0: return [4 /*yield*/, fetchIssueComments(issue.number)];
|
|
520
|
-
case 1:
|
|
521
|
-
comments = _a.sent();
|
|
522
|
-
md = "# Issue #".concat(issue.number, ": ").concat(issue.title, "\n\n");
|
|
523
|
-
md += "- **Author:** ".concat(issue.user.login, "\n");
|
|
524
|
-
md += "- **State:** ".concat(issue.state, "\n");
|
|
525
|
-
md += "- **Created on:** ".concat(new Date(issue.created_at).toDateString(), "\n");
|
|
526
|
-
md += "- **Updated on:** ".concat(new Date(issue.updated_at).toDateString(), "\n");
|
|
527
|
-
md += "- **Labels:** ".concat(issue.labels.map(function (l) { return "`".concat(l.name, "`"); }).join(', ') || 'None', "\n\n");
|
|
528
|
-
md += "## Description\n\n".concat(issue.body || '_No description._', "\n\n## Comments\n\n");
|
|
529
|
-
if (comments.length === 0) {
|
|
530
|
-
md += '_No comments._\n';
|
|
531
|
-
}
|
|
532
|
-
else {
|
|
533
|
-
for (_i = 0, comments_1 = comments; _i < comments_1.length; _i++) {
|
|
534
|
-
c = comments_1[_i];
|
|
535
|
-
md += "### ".concat(c.user.login, " - ").concat(new Date(c.created_at).toDateString(), "\n\n").concat(c.body, "\n\n---\n\n");
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
return [2 /*return*/, md];
|
|
539
|
-
}
|
|
540
|
-
});
|
|
541
|
-
}); };
|
|
542
|
-
processIssue = function (issue) { return __awaiter(_this, void 0, void 0, function () {
|
|
543
|
-
var issueNumber, url, markdown, issueConfig, chunks, _i, chunks_1, chunk, chunkHash, chunkId, checkHashStmt, existing, embeddings, pointId, existingPoints, embeddings, error_5;
|
|
544
|
-
return __generator(this, function (_a) {
|
|
545
|
-
switch (_a.label) {
|
|
546
|
-
case 0:
|
|
547
|
-
issueNumber = issue.number;
|
|
548
|
-
url = "https://github.com/".concat(repo, "/issues/").concat(issueNumber);
|
|
549
|
-
logger.info("Processing issue #".concat(issueNumber));
|
|
550
|
-
return [4 /*yield*/, generateMarkdownForIssue(issue)];
|
|
551
|
-
case 1:
|
|
552
|
-
markdown = _a.sent();
|
|
553
|
-
issueConfig = __assign(__assign({}, sourceConfig), { product_name: sourceConfig.product_name || repo, max_size: sourceConfig.max_size || Infinity });
|
|
554
|
-
return [4 /*yield*/, this.chunkMarkdown(markdown, issueConfig, url)];
|
|
555
|
-
case 2:
|
|
556
|
-
chunks = _a.sent();
|
|
557
|
-
logger.info("Issue #".concat(issueNumber, ": Created ").concat(chunks.length, " chunks"));
|
|
558
|
-
_i = 0, chunks_1 = chunks;
|
|
559
|
-
_a.label = 3;
|
|
560
|
-
case 3:
|
|
561
|
-
if (!(_i < chunks_1.length)) return [3 /*break*/, 14];
|
|
562
|
-
chunk = chunks_1[_i];
|
|
563
|
-
chunkHash = this.generateHash(chunk.content);
|
|
564
|
-
chunkId = chunk.metadata.chunk_id.substring(0, 8) + '...';
|
|
565
|
-
if (!(dbConnection.type === 'sqlite')) return [3 /*break*/, 5];
|
|
566
|
-
checkHashStmt = this.prepareSQLiteStatements(dbConnection.db).checkHashStmt;
|
|
567
|
-
existing = checkHashStmt.get(chunk.metadata.chunk_id);
|
|
568
|
-
if (existing && existing.hash === chunkHash) {
|
|
569
|
-
logger.info("Skipping unchanged chunk: ".concat(chunkId));
|
|
570
|
-
return [3 /*break*/, 13];
|
|
571
|
-
}
|
|
572
|
-
return [4 /*yield*/, this.createEmbeddings([chunk.content])];
|
|
573
|
-
case 4:
|
|
574
|
-
embeddings = _a.sent();
|
|
575
|
-
if (embeddings.length) {
|
|
576
|
-
this.insertVectorsSQLite(dbConnection.db, chunk, embeddings[0], logger, chunkHash);
|
|
577
|
-
logger.debug("Stored chunk ".concat(chunkId, " in SQLite"));
|
|
578
|
-
}
|
|
579
|
-
else {
|
|
580
|
-
logger.error("Embedding failed for chunk: ".concat(chunkId));
|
|
581
|
-
}
|
|
582
|
-
return [3 /*break*/, 13];
|
|
583
|
-
case 5:
|
|
584
|
-
if (!(dbConnection.type === 'qdrant')) return [3 /*break*/, 13];
|
|
585
|
-
_a.label = 6;
|
|
586
|
-
case 6:
|
|
587
|
-
_a.trys.push([6, 12, , 13]);
|
|
588
|
-
pointId = void 0;
|
|
589
|
-
try {
|
|
590
|
-
pointId = chunk.metadata.chunk_id;
|
|
591
|
-
if (!this.isValidUuid(pointId)) {
|
|
592
|
-
pointId = this.hashToUuid(chunk.metadata.chunk_id);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
catch (e) {
|
|
596
|
-
pointId = crypto_1.default.randomUUID();
|
|
597
|
-
}
|
|
598
|
-
return [4 /*yield*/, dbConnection.client.retrieve(dbConnection.collectionName, {
|
|
599
|
-
ids: [pointId],
|
|
600
|
-
with_payload: true,
|
|
601
|
-
with_vector: false,
|
|
602
|
-
})];
|
|
603
|
-
case 7:
|
|
604
|
-
existingPoints = _a.sent();
|
|
605
|
-
if (existingPoints.length > 0 && existingPoints[0].payload && existingPoints[0].payload.hash === chunkHash) {
|
|
606
|
-
logger.info("Skipping unchanged chunk: ".concat(chunkId));
|
|
607
|
-
return [3 /*break*/, 13];
|
|
608
|
-
}
|
|
609
|
-
return [4 /*yield*/, this.createEmbeddings([chunk.content])];
|
|
610
|
-
case 8:
|
|
611
|
-
embeddings = _a.sent();
|
|
612
|
-
if (!embeddings.length) return [3 /*break*/, 10];
|
|
613
|
-
return [4 /*yield*/, this.storeChunkInQdrant(dbConnection, chunk, embeddings[0], chunkHash)];
|
|
614
|
-
case 9:
|
|
615
|
-
_a.sent();
|
|
616
|
-
logger.debug("Stored chunk ".concat(chunkId, " in Qdrant (").concat(dbConnection.collectionName, ")"));
|
|
617
|
-
return [3 /*break*/, 11];
|
|
618
|
-
case 10:
|
|
619
|
-
logger.error("Embedding failed for chunk: ".concat(chunkId));
|
|
620
|
-
_a.label = 11;
|
|
621
|
-
case 11: return [3 /*break*/, 13];
|
|
622
|
-
case 12:
|
|
623
|
-
error_5 = _a.sent();
|
|
624
|
-
logger.error("Error processing chunk in Qdrant:", error_5);
|
|
625
|
-
return [3 /*break*/, 13];
|
|
626
|
-
case 13:
|
|
627
|
-
_i++;
|
|
628
|
-
return [3 /*break*/, 3];
|
|
629
|
-
case 14: return [2 /*return*/];
|
|
630
|
-
}
|
|
631
|
-
});
|
|
632
|
-
}); };
|
|
633
|
-
logger.info("Fetching GitHub issues for ".concat(repo, " since ").concat(lastRunDate));
|
|
634
|
-
return [4 /*yield*/, fetchAllIssues(lastRunDate)];
|
|
635
|
-
case 3:
|
|
636
|
-
issues = _b.sent();
|
|
637
|
-
logger.info("Found ".concat(issues.length, " updated/new issues"));
|
|
638
|
-
i = 0;
|
|
639
|
-
_b.label = 4;
|
|
640
|
-
case 4:
|
|
641
|
-
if (!(i < issues.length)) return [3 /*break*/, 7];
|
|
642
|
-
logger.info("Processing issue ".concat(i + 1, "/").concat(issues.length));
|
|
643
|
-
return [4 /*yield*/, processIssue(issues[i])];
|
|
644
|
-
case 5:
|
|
645
|
-
_b.sent();
|
|
646
|
-
_b.label = 6;
|
|
647
|
-
case 6:
|
|
648
|
-
i++;
|
|
649
|
-
return [3 /*break*/, 4];
|
|
650
|
-
case 7:
|
|
651
|
-
// Update the last run date in the database after processing all issues
|
|
652
|
-
return [4 /*yield*/, this.updateLastRunDate(dbConnection, repo)];
|
|
653
|
-
case 8:
|
|
654
|
-
// Update the last run date in the database after processing all issues
|
|
655
|
-
_b.sent();
|
|
656
|
-
logger.info("Successfully processed ".concat(issues.length, " issues"));
|
|
657
|
-
return [2 /*return*/];
|
|
658
|
-
}
|
|
659
|
-
});
|
|
660
|
-
});
|
|
661
|
-
};
|
|
662
|
-
Doc2Vec.prototype.processGithubRepo = function (config, parentLogger) {
|
|
663
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
664
|
-
var logger, dbConnection;
|
|
665
|
-
return __generator(this, function (_a) {
|
|
666
|
-
switch (_a.label) {
|
|
667
|
-
case 0:
|
|
668
|
-
logger = parentLogger.child('process');
|
|
669
|
-
logger.info("Starting processing for GitHub repo: ".concat(config.repo));
|
|
670
|
-
return [4 /*yield*/, this.initDatabase(config, logger)];
|
|
671
|
-
case 1:
|
|
672
|
-
dbConnection = _a.sent();
|
|
673
|
-
// Initialize metadata storage
|
|
674
|
-
return [4 /*yield*/, this.initDatabaseMetadata(dbConnection)];
|
|
675
|
-
case 2:
|
|
676
|
-
// Initialize metadata storage
|
|
677
|
-
_a.sent();
|
|
678
|
-
logger.section('GITHUB ISSUES');
|
|
679
|
-
// Process GitHub issues
|
|
680
|
-
return [4 /*yield*/, this.fetchAndProcessGitHubIssues(config.repo, config, dbConnection, logger)];
|
|
681
|
-
case 3:
|
|
682
|
-
// Process GitHub issues
|
|
683
|
-
_a.sent();
|
|
684
|
-
logger.info("Finished processing GitHub repo: ".concat(config.repo));
|
|
685
|
-
return [2 /*return*/];
|
|
686
|
-
}
|
|
687
|
-
});
|
|
688
|
-
});
|
|
689
|
-
};
|
|
690
|
-
Doc2Vec.prototype.processWebsite = function (config, parentLogger) {
|
|
691
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
692
|
-
var logger, dbConnection, validChunkIds, visitedUrls, urlPrefix;
|
|
693
|
-
var _this = this;
|
|
694
|
-
return __generator(this, function (_a) {
|
|
695
|
-
switch (_a.label) {
|
|
696
|
-
case 0:
|
|
697
|
-
logger = parentLogger.child('process');
|
|
698
|
-
logger.info("Starting processing for website: ".concat(config.url));
|
|
699
|
-
return [4 /*yield*/, this.initDatabase(config, logger)];
|
|
700
|
-
case 1:
|
|
701
|
-
dbConnection = _a.sent();
|
|
702
|
-
validChunkIds = new Set();
|
|
703
|
-
visitedUrls = new Set();
|
|
704
|
-
urlPrefix = this.getUrlPrefix(config.url);
|
|
705
|
-
logger.section('CRAWL AND EMBEDDING');
|
|
706
|
-
return [4 /*yield*/, this.crawlWebsite(config.url, config, function (url, content) { return __awaiter(_this, void 0, void 0, function () {
|
|
707
|
-
var chunks, chunkProgress, i, chunk, chunkId, needsEmbedding, chunkHash, checkHashStmt, existing, pointId, existingPoints, error_6, embeddings, embedding, error_7;
|
|
708
|
-
return __generator(this, function (_a) {
|
|
709
|
-
switch (_a.label) {
|
|
710
|
-
case 0:
|
|
711
|
-
visitedUrls.add(url);
|
|
712
|
-
logger.info("Processing content from ".concat(url, " (").concat(content.length, " chars markdown)"));
|
|
713
|
-
_a.label = 1;
|
|
714
|
-
case 1:
|
|
715
|
-
_a.trys.push([1, 17, , 18]);
|
|
716
|
-
return [4 /*yield*/, this.chunkMarkdown(content, config, url)];
|
|
717
|
-
case 2:
|
|
718
|
-
chunks = _a.sent();
|
|
719
|
-
logger.info("Created ".concat(chunks.length, " chunks"));
|
|
720
|
-
if (!(chunks.length > 0)) return [3 /*break*/, 16];
|
|
721
|
-
chunkProgress = logger.progress("Embedding chunks for ".concat(url), chunks.length);
|
|
722
|
-
i = 0;
|
|
723
|
-
_a.label = 3;
|
|
724
|
-
case 3:
|
|
725
|
-
if (!(i < chunks.length)) return [3 /*break*/, 15];
|
|
726
|
-
chunk = chunks[i];
|
|
727
|
-
validChunkIds.add(chunk.metadata.chunk_id);
|
|
728
|
-
chunkId = chunk.metadata.chunk_id.substring(0, 8) + '...';
|
|
729
|
-
needsEmbedding = true;
|
|
730
|
-
chunkHash = this.generateHash(chunk.content);
|
|
731
|
-
if (!(dbConnection.type === 'sqlite')) return [3 /*break*/, 4];
|
|
732
|
-
checkHashStmt = this.prepareSQLiteStatements(dbConnection.db).checkHashStmt;
|
|
733
|
-
existing = checkHashStmt.get(chunk.metadata.chunk_id);
|
|
734
|
-
if (existing && existing.hash === chunkHash) {
|
|
735
|
-
needsEmbedding = false;
|
|
736
|
-
chunkProgress.update(1, "Skipping unchanged chunk ".concat(chunkId));
|
|
737
|
-
logger.info("Skipping unchanged chunk: ".concat(chunkId));
|
|
738
|
-
}
|
|
739
|
-
return [3 /*break*/, 8];
|
|
740
|
-
case 4:
|
|
741
|
-
if (!(dbConnection.type === 'qdrant')) return [3 /*break*/, 8];
|
|
742
|
-
_a.label = 5;
|
|
743
|
-
case 5:
|
|
744
|
-
_a.trys.push([5, 7, , 8]);
|
|
745
|
-
pointId = void 0;
|
|
746
|
-
try {
|
|
747
|
-
pointId = chunk.metadata.chunk_id;
|
|
748
|
-
if (!this.isValidUuid(pointId)) {
|
|
749
|
-
pointId = this.hashToUuid(chunk.metadata.chunk_id);
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
catch (e) {
|
|
753
|
-
pointId = crypto_1.default.randomUUID();
|
|
754
|
-
}
|
|
755
|
-
return [4 /*yield*/, dbConnection.client.retrieve(dbConnection.collectionName, {
|
|
756
|
-
ids: [pointId],
|
|
757
|
-
with_payload: true,
|
|
758
|
-
with_vector: false,
|
|
759
|
-
})];
|
|
760
|
-
case 6:
|
|
761
|
-
existingPoints = _a.sent();
|
|
762
|
-
if (existingPoints.length > 0 && existingPoints[0].payload && existingPoints[0].payload.hash === chunkHash) {
|
|
763
|
-
needsEmbedding = false;
|
|
764
|
-
chunkProgress.update(1, "Skipping unchanged chunk ".concat(chunkId));
|
|
765
|
-
logger.info("Skipping unchanged chunk: ".concat(chunkId));
|
|
766
|
-
}
|
|
767
|
-
return [3 /*break*/, 8];
|
|
768
|
-
case 7:
|
|
769
|
-
error_6 = _a.sent();
|
|
770
|
-
logger.error("Error checking existing point in Qdrant:", error_6);
|
|
771
|
-
return [3 /*break*/, 8];
|
|
772
|
-
case 8:
|
|
773
|
-
if (!needsEmbedding) return [3 /*break*/, 14];
|
|
774
|
-
return [4 /*yield*/, this.createEmbeddings([chunk.content])];
|
|
775
|
-
case 9:
|
|
776
|
-
embeddings = _a.sent();
|
|
777
|
-
if (!(embeddings.length > 0)) return [3 /*break*/, 13];
|
|
778
|
-
embedding = embeddings[0];
|
|
779
|
-
if (!(dbConnection.type === 'sqlite')) return [3 /*break*/, 10];
|
|
780
|
-
this.insertVectorsSQLite(dbConnection.db, chunk, embedding, logger, chunkHash);
|
|
781
|
-
chunkProgress.update(1, "Stored chunk ".concat(chunkId, " in SQLite"));
|
|
782
|
-
return [3 /*break*/, 12];
|
|
783
|
-
case 10:
|
|
784
|
-
if (!(dbConnection.type === 'qdrant')) return [3 /*break*/, 12];
|
|
785
|
-
return [4 /*yield*/, this.storeChunkInQdrant(dbConnection, chunk, embedding, chunkHash)];
|
|
786
|
-
case 11:
|
|
787
|
-
_a.sent();
|
|
788
|
-
chunkProgress.update(1, "Stored chunk ".concat(chunkId, " in Qdrant (").concat(dbConnection.collectionName, ")"));
|
|
789
|
-
_a.label = 12;
|
|
790
|
-
case 12: return [3 /*break*/, 14];
|
|
791
|
-
case 13:
|
|
792
|
-
logger.error("Embedding failed for chunk: ".concat(chunkId));
|
|
793
|
-
chunkProgress.update(1, "Failed to embed chunk ".concat(chunkId));
|
|
794
|
-
_a.label = 14;
|
|
795
|
-
case 14:
|
|
796
|
-
i++;
|
|
797
|
-
return [3 /*break*/, 3];
|
|
798
|
-
case 15:
|
|
799
|
-
chunkProgress.complete();
|
|
800
|
-
_a.label = 16;
|
|
801
|
-
case 16: return [3 /*break*/, 18];
|
|
802
|
-
case 17:
|
|
803
|
-
error_7 = _a.sent();
|
|
804
|
-
logger.error("Error during chunking or embedding for ".concat(url, ":"), error_7);
|
|
805
|
-
return [3 /*break*/, 18];
|
|
806
|
-
case 18: return [2 /*return*/];
|
|
807
|
-
}
|
|
808
|
-
});
|
|
809
|
-
}); }, logger, visitedUrls)];
|
|
810
|
-
case 2:
|
|
811
|
-
_a.sent();
|
|
812
|
-
logger.info("Found ".concat(validChunkIds.size, " valid chunks across processed pages for ").concat(config.url));
|
|
813
|
-
logger.section('CLEANUP');
|
|
814
|
-
if (!(dbConnection.type === 'sqlite')) return [3 /*break*/, 3];
|
|
815
|
-
logger.info("Running SQLite cleanup for ".concat(urlPrefix));
|
|
816
|
-
this.removeObsoleteChunksSQLite(dbConnection.db, visitedUrls, urlPrefix, logger);
|
|
817
|
-
return [3 /*break*/, 5];
|
|
818
|
-
case 3:
|
|
819
|
-
if (!(dbConnection.type === 'qdrant')) return [3 /*break*/, 5];
|
|
820
|
-
logger.info("Running Qdrant cleanup for ".concat(urlPrefix, " in collection ").concat(dbConnection.collectionName));
|
|
821
|
-
return [4 /*yield*/, this.removeObsoleteChunksQdrant(dbConnection, visitedUrls, urlPrefix, logger)];
|
|
822
|
-
case 4:
|
|
823
|
-
_a.sent();
|
|
824
|
-
_a.label = 5;
|
|
825
|
-
case 5:
|
|
826
|
-
logger.info("Finished processing website: ".concat(config.url));
|
|
827
|
-
return [2 /*return*/];
|
|
828
|
-
}
|
|
829
|
-
});
|
|
830
|
-
});
|
|
831
|
-
};
|
|
832
|
-
Doc2Vec.prototype.processLocalDirectory = function (config, parentLogger) {
|
|
833
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
834
|
-
var logger, dbConnection, validChunkIds, processedFiles;
|
|
835
|
-
var _this = this;
|
|
836
|
-
return __generator(this, function (_a) {
|
|
837
|
-
switch (_a.label) {
|
|
838
|
-
case 0:
|
|
839
|
-
logger = parentLogger.child('process');
|
|
840
|
-
logger.info("Starting processing for local directory: ".concat(config.path));
|
|
841
|
-
return [4 /*yield*/, this.initDatabase(config, logger)];
|
|
842
|
-
case 1:
|
|
843
|
-
dbConnection = _a.sent();
|
|
844
|
-
validChunkIds = new Set();
|
|
845
|
-
processedFiles = new Set();
|
|
846
|
-
logger.section('FILE SCANNING AND EMBEDDING');
|
|
847
|
-
return [4 /*yield*/, this.processDirectory(config.path, config, function (filePath, content) { return __awaiter(_this, void 0, void 0, function () {
|
|
848
|
-
var fileUrl, chunks, chunkProgress, i, chunk, chunkId, needsEmbedding, chunkHash, checkHashStmt, existing, pointId, existingPoints, error_8, embeddings, embedding, error_9;
|
|
849
|
-
return __generator(this, function (_a) {
|
|
850
|
-
switch (_a.label) {
|
|
851
|
-
case 0:
|
|
852
|
-
processedFiles.add(filePath);
|
|
853
|
-
logger.info("Processing content from ".concat(filePath, " (").concat(content.length, " chars)"));
|
|
854
|
-
_a.label = 1;
|
|
855
|
-
case 1:
|
|
856
|
-
_a.trys.push([1, 17, , 18]);
|
|
857
|
-
fileUrl = "file://".concat(filePath);
|
|
858
|
-
return [4 /*yield*/, this.chunkMarkdown(content, config, fileUrl)];
|
|
859
|
-
case 2:
|
|
860
|
-
chunks = _a.sent();
|
|
861
|
-
logger.info("Created ".concat(chunks.length, " chunks"));
|
|
862
|
-
if (!(chunks.length > 0)) return [3 /*break*/, 16];
|
|
863
|
-
chunkProgress = logger.progress("Embedding chunks for ".concat(filePath), chunks.length);
|
|
864
|
-
i = 0;
|
|
865
|
-
_a.label = 3;
|
|
866
|
-
case 3:
|
|
867
|
-
if (!(i < chunks.length)) return [3 /*break*/, 15];
|
|
868
|
-
chunk = chunks[i];
|
|
869
|
-
validChunkIds.add(chunk.metadata.chunk_id);
|
|
870
|
-
chunkId = chunk.metadata.chunk_id.substring(0, 8) + '...';
|
|
871
|
-
needsEmbedding = true;
|
|
872
|
-
chunkHash = this.generateHash(chunk.content);
|
|
873
|
-
if (!(dbConnection.type === 'sqlite')) return [3 /*break*/, 4];
|
|
874
|
-
checkHashStmt = this.prepareSQLiteStatements(dbConnection.db).checkHashStmt;
|
|
875
|
-
existing = checkHashStmt.get(chunk.metadata.chunk_id);
|
|
876
|
-
if (existing && existing.hash === chunkHash) {
|
|
877
|
-
needsEmbedding = false;
|
|
878
|
-
chunkProgress.update(1, "Skipping unchanged chunk ".concat(chunkId));
|
|
879
|
-
logger.info("Skipping unchanged chunk: ".concat(chunkId));
|
|
880
|
-
}
|
|
881
|
-
return [3 /*break*/, 8];
|
|
882
|
-
case 4:
|
|
883
|
-
if (!(dbConnection.type === 'qdrant')) return [3 /*break*/, 8];
|
|
884
|
-
_a.label = 5;
|
|
885
|
-
case 5:
|
|
886
|
-
_a.trys.push([5, 7, , 8]);
|
|
887
|
-
pointId = void 0;
|
|
888
|
-
try {
|
|
889
|
-
pointId = chunk.metadata.chunk_id;
|
|
890
|
-
if (!this.isValidUuid(pointId)) {
|
|
891
|
-
pointId = this.hashToUuid(chunk.metadata.chunk_id);
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
catch (e) {
|
|
895
|
-
pointId = crypto_1.default.randomUUID();
|
|
896
|
-
}
|
|
897
|
-
return [4 /*yield*/, dbConnection.client.retrieve(dbConnection.collectionName, {
|
|
898
|
-
ids: [pointId],
|
|
899
|
-
with_payload: true,
|
|
900
|
-
with_vector: false,
|
|
901
|
-
})];
|
|
902
|
-
case 6:
|
|
903
|
-
existingPoints = _a.sent();
|
|
904
|
-
if (existingPoints.length > 0 && existingPoints[0].payload && existingPoints[0].payload.hash === chunkHash) {
|
|
905
|
-
needsEmbedding = false;
|
|
906
|
-
chunkProgress.update(1, "Skipping unchanged chunk ".concat(chunkId));
|
|
907
|
-
logger.info("Skipping unchanged chunk: ".concat(chunkId));
|
|
908
|
-
}
|
|
909
|
-
return [3 /*break*/, 8];
|
|
910
|
-
case 7:
|
|
911
|
-
error_8 = _a.sent();
|
|
912
|
-
logger.error("Error checking existing point in Qdrant:", error_8);
|
|
913
|
-
return [3 /*break*/, 8];
|
|
914
|
-
case 8:
|
|
915
|
-
if (!needsEmbedding) return [3 /*break*/, 14];
|
|
916
|
-
return [4 /*yield*/, this.createEmbeddings([chunk.content])];
|
|
917
|
-
case 9:
|
|
918
|
-
embeddings = _a.sent();
|
|
919
|
-
if (!(embeddings.length > 0)) return [3 /*break*/, 13];
|
|
920
|
-
embedding = embeddings[0];
|
|
921
|
-
if (!(dbConnection.type === 'sqlite')) return [3 /*break*/, 10];
|
|
922
|
-
this.insertVectorsSQLite(dbConnection.db, chunk, embedding, logger, chunkHash);
|
|
923
|
-
chunkProgress.update(1, "Stored chunk ".concat(chunkId, " in SQLite"));
|
|
924
|
-
return [3 /*break*/, 12];
|
|
925
|
-
case 10:
|
|
926
|
-
if (!(dbConnection.type === 'qdrant')) return [3 /*break*/, 12];
|
|
927
|
-
return [4 /*yield*/, this.storeChunkInQdrant(dbConnection, chunk, embedding, chunkHash)];
|
|
928
|
-
case 11:
|
|
929
|
-
_a.sent();
|
|
930
|
-
chunkProgress.update(1, "Stored chunk ".concat(chunkId, " in Qdrant (").concat(dbConnection.collectionName, ")"));
|
|
931
|
-
_a.label = 12;
|
|
932
|
-
case 12: return [3 /*break*/, 14];
|
|
933
|
-
case 13:
|
|
934
|
-
logger.error("Embedding failed for chunk: ".concat(chunkId));
|
|
935
|
-
chunkProgress.update(1, "Failed to embed chunk ".concat(chunkId));
|
|
936
|
-
_a.label = 14;
|
|
937
|
-
case 14:
|
|
938
|
-
i++;
|
|
939
|
-
return [3 /*break*/, 3];
|
|
940
|
-
case 15:
|
|
941
|
-
chunkProgress.complete();
|
|
942
|
-
_a.label = 16;
|
|
943
|
-
case 16: return [3 /*break*/, 18];
|
|
944
|
-
case 17:
|
|
945
|
-
error_9 = _a.sent();
|
|
946
|
-
logger.error("Error during chunking or embedding for ".concat(filePath, ":"), error_9);
|
|
947
|
-
return [3 /*break*/, 18];
|
|
948
|
-
case 18: return [2 /*return*/];
|
|
949
|
-
}
|
|
950
|
-
});
|
|
951
|
-
}); }, logger)];
|
|
952
|
-
case 2:
|
|
953
|
-
_a.sent();
|
|
954
|
-
logger.info("Found ".concat(validChunkIds.size, " valid chunks across processed files for ").concat(config.path));
|
|
955
|
-
logger.section('CLEANUP');
|
|
956
|
-
if (!(dbConnection.type === 'sqlite')) return [3 /*break*/, 3];
|
|
957
|
-
logger.info("Running SQLite cleanup for local directory ".concat(config.path));
|
|
958
|
-
this.removeObsoleteFilesSQLite(dbConnection.db, processedFiles, config.path, logger);
|
|
959
|
-
return [3 /*break*/, 5];
|
|
960
|
-
case 3:
|
|
961
|
-
if (!(dbConnection.type === 'qdrant')) return [3 /*break*/, 5];
|
|
962
|
-
logger.info("Running Qdrant cleanup for local directory ".concat(config.path, " in collection ").concat(dbConnection.collectionName));
|
|
963
|
-
return [4 /*yield*/, this.removeObsoleteFilesQdrant(dbConnection, processedFiles, config.path, logger)];
|
|
964
|
-
case 4:
|
|
965
|
-
_a.sent();
|
|
966
|
-
_a.label = 5;
|
|
967
|
-
case 5:
|
|
968
|
-
logger.info("Finished processing local directory: ".concat(config.path));
|
|
969
|
-
return [2 /*return*/];
|
|
970
|
-
}
|
|
971
|
-
});
|
|
972
|
-
});
|
|
973
|
-
};
|
|
974
|
-
Doc2Vec.prototype.processDirectory = function (dirPath_1, config_1, processFileContent_1, parentLogger_1) {
|
|
975
|
-
return __awaiter(this, arguments, void 0, function (dirPath, config, processFileContent, parentLogger, visitedPaths) {
|
|
976
|
-
var logger, recursive, includeExtensions, excludeExtensions, encoding, files, processedFiles, skippedFiles, _i, files_1, file, filePath, stat, extension, content, processedContent, cleanHtml, error_10, error_11;
|
|
977
|
-
if (visitedPaths === void 0) { visitedPaths = new Set(); }
|
|
978
|
-
return __generator(this, function (_a) {
|
|
979
|
-
switch (_a.label) {
|
|
980
|
-
case 0:
|
|
981
|
-
logger = parentLogger.child('directory-processor');
|
|
982
|
-
logger.info("Processing directory: ".concat(dirPath));
|
|
983
|
-
recursive = config.recursive !== undefined ? config.recursive : true;
|
|
984
|
-
includeExtensions = config.include_extensions || ['.md', '.txt', '.html', '.htm'];
|
|
985
|
-
excludeExtensions = config.exclude_extensions || [];
|
|
986
|
-
encoding = config.encoding || 'utf8';
|
|
987
|
-
_a.label = 1;
|
|
988
|
-
case 1:
|
|
989
|
-
_a.trys.push([1, 12, , 13]);
|
|
990
|
-
files = fs.readdirSync(dirPath);
|
|
991
|
-
processedFiles = 0;
|
|
992
|
-
skippedFiles = 0;
|
|
993
|
-
_i = 0, files_1 = files;
|
|
994
|
-
_a.label = 2;
|
|
995
|
-
case 2:
|
|
996
|
-
if (!(_i < files_1.length)) return [3 /*break*/, 11];
|
|
997
|
-
file = files_1[_i];
|
|
998
|
-
filePath = path.join(dirPath, file);
|
|
999
|
-
stat = fs.statSync(filePath);
|
|
1000
|
-
// Skip already visited paths
|
|
1001
|
-
if (visitedPaths.has(filePath)) {
|
|
1002
|
-
logger.debug("Skipping already visited path: ".concat(filePath));
|
|
1003
|
-
return [3 /*break*/, 10];
|
|
1004
|
-
}
|
|
1005
|
-
visitedPaths.add(filePath);
|
|
1006
|
-
if (!stat.isDirectory()) return [3 /*break*/, 6];
|
|
1007
|
-
if (!recursive) return [3 /*break*/, 4];
|
|
1008
|
-
return [4 /*yield*/, this.processDirectory(filePath, config, processFileContent, logger, visitedPaths)];
|
|
1009
|
-
case 3:
|
|
1010
|
-
_a.sent();
|
|
1011
|
-
return [3 /*break*/, 5];
|
|
1012
|
-
case 4:
|
|
1013
|
-
logger.debug("Skipping directory ".concat(filePath, " (recursive=false)"));
|
|
1014
|
-
_a.label = 5;
|
|
1015
|
-
case 5: return [3 /*break*/, 10];
|
|
1016
|
-
case 6:
|
|
1017
|
-
if (!stat.isFile()) return [3 /*break*/, 10];
|
|
1018
|
-
extension = path.extname(file).toLowerCase();
|
|
1019
|
-
// Apply extension filters
|
|
1020
|
-
if (excludeExtensions.includes(extension)) {
|
|
1021
|
-
logger.debug("Skipping file with excluded extension: ".concat(filePath));
|
|
1022
|
-
skippedFiles++;
|
|
1023
|
-
return [3 /*break*/, 10];
|
|
1024
|
-
}
|
|
1025
|
-
if (includeExtensions.length > 0 && !includeExtensions.includes(extension)) {
|
|
1026
|
-
logger.debug("Skipping file with non-included extension: ".concat(filePath));
|
|
1027
|
-
skippedFiles++;
|
|
1028
|
-
return [3 /*break*/, 10];
|
|
1029
|
-
}
|
|
1030
|
-
_a.label = 7;
|
|
1031
|
-
case 7:
|
|
1032
|
-
_a.trys.push([7, 9, , 10]);
|
|
1033
|
-
logger.info("Reading file: ".concat(filePath));
|
|
1034
|
-
content = fs.readFileSync(filePath, { encoding: encoding });
|
|
1035
|
-
if (content.length > config.max_size) {
|
|
1036
|
-
logger.warn("File content (".concat(content.length, " chars) exceeds max size (").concat(config.max_size, "). Skipping ").concat(filePath, "."));
|
|
1037
|
-
skippedFiles++;
|
|
1038
|
-
return [3 /*break*/, 10];
|
|
1039
|
-
}
|
|
1040
|
-
processedContent = void 0;
|
|
1041
|
-
if (extension === '.html' || extension === '.htm') {
|
|
1042
|
-
logger.debug("Converting HTML to Markdown for ".concat(filePath));
|
|
1043
|
-
cleanHtml = (0, sanitize_html_1.default)(content, {
|
|
1044
|
-
allowedTags: [
|
|
1045
|
-
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'a', 'ul', 'ol',
|
|
1046
|
-
'li', 'b', 'i', 'strong', 'em', 'code', 'pre',
|
|
1047
|
-
'div', 'span', 'table', 'thead', 'tbody', 'tr', 'th', 'td'
|
|
1048
|
-
],
|
|
1049
|
-
allowedAttributes: {
|
|
1050
|
-
'a': ['href'],
|
|
1051
|
-
'pre': ['class', 'data-language'],
|
|
1052
|
-
'code': ['class', 'data-language'],
|
|
1053
|
-
'div': ['class'],
|
|
1054
|
-
'span': ['class']
|
|
1055
|
-
}
|
|
1056
|
-
});
|
|
1057
|
-
processedContent = this.turndownService.turndown(cleanHtml);
|
|
1058
|
-
}
|
|
1059
|
-
else {
|
|
1060
|
-
processedContent = content;
|
|
1061
|
-
}
|
|
1062
|
-
return [4 /*yield*/, processFileContent(filePath, processedContent)];
|
|
1063
|
-
case 8:
|
|
1064
|
-
_a.sent();
|
|
1065
|
-
processedFiles++;
|
|
1066
|
-
return [3 /*break*/, 10];
|
|
1067
|
-
case 9:
|
|
1068
|
-
error_10 = _a.sent();
|
|
1069
|
-
logger.error("Error processing file ".concat(filePath, ":"), error_10);
|
|
1070
|
-
return [3 /*break*/, 10];
|
|
1071
|
-
case 10:
|
|
1072
|
-
_i++;
|
|
1073
|
-
return [3 /*break*/, 2];
|
|
1074
|
-
case 11:
|
|
1075
|
-
logger.info("Directory processed. Processed: ".concat(processedFiles, ", Skipped: ").concat(skippedFiles));
|
|
1076
|
-
return [3 /*break*/, 13];
|
|
1077
|
-
case 12:
|
|
1078
|
-
error_11 = _a.sent();
|
|
1079
|
-
logger.error("Error reading directory ".concat(dirPath, ":"), error_11);
|
|
1080
|
-
return [3 /*break*/, 13];
|
|
1081
|
-
case 13: return [2 /*return*/];
|
|
1082
|
-
}
|
|
1083
|
-
});
|
|
1084
|
-
});
|
|
1085
|
-
};
|
|
1086
|
-
Doc2Vec.prototype.initDatabase = function (config, parentLogger) {
|
|
1087
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1088
|
-
var logger, dbConfig, params, dbPath, db, params, qdrantUrl, qdrantPort, collectionName, qdrantClient, errMsg;
|
|
1089
|
-
return __generator(this, function (_a) {
|
|
1090
|
-
switch (_a.label) {
|
|
1091
|
-
case 0:
|
|
1092
|
-
logger = parentLogger.child('database');
|
|
1093
|
-
dbConfig = config.database_config;
|
|
1094
|
-
if (!(dbConfig.type === 'sqlite')) return [3 /*break*/, 1];
|
|
1095
|
-
params = dbConfig.params;
|
|
1096
|
-
dbPath = params.db_path || path.join(process.cwd(), "".concat(config.product_name.replace(/\s+/g, '_'), "-").concat(config.version, ".db"));
|
|
1097
|
-
logger.info("Opening SQLite database at ".concat(dbPath));
|
|
1098
|
-
db = new better_sqlite3_1.default(dbPath, { allowExtension: true });
|
|
1099
|
-
sqliteVec.load(db);
|
|
1100
|
-
logger.debug("Creating vec_items table if it doesn't exist");
|
|
1101
|
-
db.exec("\n CREATE VIRTUAL TABLE IF NOT EXISTS vec_items USING vec0(\n embedding FLOAT[3072],\n product_name TEXT,\n version TEXT,\n heading_hierarchy TEXT,\n section TEXT,\n chunk_id TEXT UNIQUE,\n content TEXT,\n url TEXT,\n hash TEXT\n );\n ");
|
|
1102
|
-
logger.info("SQLite database initialized successfully");
|
|
1103
|
-
return [2 /*return*/, { db: db, type: 'sqlite' }];
|
|
1104
|
-
case 1:
|
|
1105
|
-
if (!(dbConfig.type === 'qdrant')) return [3 /*break*/, 3];
|
|
1106
|
-
params = dbConfig.params;
|
|
1107
|
-
qdrantUrl = params.qdrant_url || 'http://localhost:6333';
|
|
1108
|
-
qdrantPort = params.qdrant_port || 443;
|
|
1109
|
-
collectionName = params.collection_name || "".concat(config.product_name.toLowerCase().replace(/\s+/g, '_'), "_").concat(config.version);
|
|
1110
|
-
logger.info("Connecting to Qdrant at ".concat(qdrantUrl, ":").concat(qdrantPort, ", collection: ").concat(collectionName));
|
|
1111
|
-
qdrantClient = new js_client_rest_1.QdrantClient({ url: qdrantUrl, apiKey: process.env.QDRANT_API_KEY, port: qdrantPort });
|
|
1112
|
-
return [4 /*yield*/, this.createCollectionQdrant(qdrantClient, collectionName, logger)];
|
|
1113
|
-
case 2:
|
|
1114
|
-
_a.sent();
|
|
1115
|
-
logger.info("Qdrant connection established successfully");
|
|
1116
|
-
return [2 /*return*/, { client: qdrantClient, collectionName: collectionName, type: 'qdrant' }];
|
|
1117
|
-
case 3:
|
|
1118
|
-
errMsg = "Unsupported database type: ".concat(dbConfig.type);
|
|
1119
|
-
logger.error(errMsg);
|
|
1120
|
-
throw new Error(errMsg);
|
|
1121
|
-
}
|
|
1122
|
-
});
|
|
1123
|
-
});
|
|
1124
|
-
};
|
|
1125
|
-
Doc2Vec.prototype.createCollectionQdrant = function (qdrantClient, collectionName, logger) {
|
|
1126
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1127
|
-
var collections, collectionExists, error_12, errorMsg, errorString;
|
|
1128
|
-
return __generator(this, function (_a) {
|
|
1129
|
-
switch (_a.label) {
|
|
1130
|
-
case 0:
|
|
1131
|
-
_a.trys.push([0, 3, , 4]);
|
|
1132
|
-
logger.debug("Checking if collection ".concat(collectionName, " exists"));
|
|
1133
|
-
return [4 /*yield*/, qdrantClient.getCollections()];
|
|
1134
|
-
case 1:
|
|
1135
|
-
collections = _a.sent();
|
|
1136
|
-
collectionExists = collections.collections.some(function (collection) { return collection.name === collectionName; });
|
|
1137
|
-
if (collectionExists) {
|
|
1138
|
-
logger.info("Collection ".concat(collectionName, " already exists"));
|
|
1139
|
-
return [2 /*return*/];
|
|
1140
|
-
}
|
|
1141
|
-
logger.info("Creating new collection ".concat(collectionName));
|
|
1142
|
-
return [4 /*yield*/, qdrantClient.createCollection(collectionName, {
|
|
1143
|
-
vectors: {
|
|
1144
|
-
size: 3072,
|
|
1145
|
-
distance: "Cosine",
|
|
1146
|
-
},
|
|
1147
|
-
})];
|
|
1148
|
-
case 2:
|
|
1149
|
-
_a.sent();
|
|
1150
|
-
logger.info("Collection ".concat(collectionName, " created successfully"));
|
|
1151
|
-
return [3 /*break*/, 4];
|
|
1152
|
-
case 3:
|
|
1153
|
-
error_12 = _a.sent();
|
|
1154
|
-
if (error_12 instanceof Error) {
|
|
1155
|
-
errorMsg = error_12.message.toLowerCase();
|
|
1156
|
-
errorString = JSON.stringify(error_12).toLowerCase();
|
|
1157
|
-
if (errorMsg.includes("already exists") ||
|
|
1158
|
-
errorString.includes("already exists") ||
|
|
1159
|
-
(error_12 === null || error_12 === void 0 ? void 0 : error_12.status) === 409 ||
|
|
1160
|
-
errorString.includes("conflict")) {
|
|
1161
|
-
logger.info("Collection ".concat(collectionName, " already exists (from error response)"));
|
|
1162
|
-
return [2 /*return*/];
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
logger.error("Error creating Qdrant collection:", error_12);
|
|
1166
|
-
logger.warn("Continuing with existing collection...");
|
|
1167
|
-
return [3 /*break*/, 4];
|
|
1168
|
-
case 4: return [2 /*return*/];
|
|
1169
|
-
}
|
|
1170
|
-
});
|
|
1171
|
-
});
|
|
1172
|
-
};
|
|
1173
|
-
Doc2Vec.prototype.prepareSQLiteStatements = function (db) {
|
|
1174
|
-
return {
|
|
1175
|
-
insertStmt: db.prepare("\n INSERT INTO vec_items (embedding, product_name, version, heading_hierarchy, section, chunk_id, content, url, hash)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n "),
|
|
1176
|
-
checkHashStmt: db.prepare("SELECT hash FROM vec_items WHERE chunk_id = ?"),
|
|
1177
|
-
updateStmt: db.prepare("\n UPDATE vec_items SET embedding = ?, product_name = ?, version = ?, heading_hierarchy = ?, section = ?, content = ?, url = ?, hash = ?\n WHERE chunk_id = ?\n "),
|
|
1178
|
-
getAllChunkIdsStmt: db.prepare("SELECT chunk_id FROM vec_items"),
|
|
1179
|
-
deleteChunkStmt: db.prepare("DELETE FROM vec_items WHERE chunk_id = ?")
|
|
1180
|
-
};
|
|
1181
|
-
};
|
|
1182
|
-
Doc2Vec.prototype.insertVectorsSQLite = function (db, chunk, embedding, logger, chunkHash) {
|
|
1183
|
-
var _a = this.prepareSQLiteStatements(db), insertStmt = _a.insertStmt, updateStmt = _a.updateStmt;
|
|
1184
|
-
var hash = chunkHash || this.generateHash(chunk.content);
|
|
1185
|
-
var transaction = db.transaction(function () {
|
|
1186
|
-
var params = [
|
|
1187
|
-
new Float32Array(embedding),
|
|
1188
|
-
chunk.metadata.product_name,
|
|
1189
|
-
chunk.metadata.version,
|
|
1190
|
-
JSON.stringify(chunk.metadata.heading_hierarchy),
|
|
1191
|
-
chunk.metadata.section,
|
|
1192
|
-
chunk.metadata.chunk_id,
|
|
1193
|
-
chunk.content,
|
|
1194
|
-
chunk.metadata.url,
|
|
1195
|
-
hash
|
|
1196
|
-
];
|
|
1197
|
-
try {
|
|
1198
|
-
insertStmt.run(params);
|
|
1199
|
-
}
|
|
1200
|
-
catch (error) {
|
|
1201
|
-
updateStmt.run(__spreadArray(__spreadArray([], params.slice(0, 8), true), [chunk.metadata.chunk_id], false));
|
|
1202
|
-
}
|
|
1203
|
-
});
|
|
1204
|
-
transaction();
|
|
1205
|
-
};
|
|
1206
|
-
Doc2Vec.prototype.storeChunkInQdrant = function (db, chunk, embedding, chunkHash) {
|
|
1207
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1208
|
-
var client, collectionName, pointId, hash, pointItem, error_13;
|
|
1209
|
-
return __generator(this, function (_a) {
|
|
1210
|
-
switch (_a.label) {
|
|
1211
|
-
case 0:
|
|
1212
|
-
client = db.client, collectionName = db.collectionName;
|
|
1213
|
-
_a.label = 1;
|
|
1214
|
-
case 1:
|
|
1215
|
-
_a.trys.push([1, 3, , 4]);
|
|
1216
|
-
pointId = void 0;
|
|
1217
|
-
try {
|
|
1218
|
-
pointId = chunk.metadata.chunk_id;
|
|
1219
|
-
if (!this.isValidUuid(pointId)) {
|
|
1220
|
-
pointId = this.hashToUuid(chunk.metadata.chunk_id);
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
catch (e) {
|
|
1224
|
-
pointId = crypto_1.default.randomUUID();
|
|
1225
|
-
}
|
|
1226
|
-
hash = chunkHash || this.generateHash(chunk.content);
|
|
1227
|
-
pointItem = {
|
|
1228
|
-
id: pointId,
|
|
1229
|
-
vector: embedding,
|
|
1230
|
-
payload: {
|
|
1231
|
-
content: chunk.content,
|
|
1232
|
-
product_name: chunk.metadata.product_name,
|
|
1233
|
-
version: chunk.metadata.version,
|
|
1234
|
-
heading_hierarchy: chunk.metadata.heading_hierarchy,
|
|
1235
|
-
section: chunk.metadata.section,
|
|
1236
|
-
url: chunk.metadata.url,
|
|
1237
|
-
hash: hash,
|
|
1238
|
-
original_chunk_id: chunk.metadata.chunk_id,
|
|
1239
|
-
},
|
|
1240
|
-
};
|
|
1241
|
-
return [4 /*yield*/, client.upsert(collectionName, {
|
|
1242
|
-
wait: true,
|
|
1243
|
-
points: [pointItem],
|
|
1244
|
-
})];
|
|
1245
|
-
case 2:
|
|
1246
|
-
_a.sent();
|
|
1247
|
-
return [3 /*break*/, 4];
|
|
1248
|
-
case 3:
|
|
1249
|
-
error_13 = _a.sent();
|
|
1250
|
-
this.logger.error("Error storing chunk in Qdrant:", error_13);
|
|
1251
|
-
return [3 /*break*/, 4];
|
|
1252
|
-
case 4: return [2 /*return*/];
|
|
1253
|
-
}
|
|
1254
|
-
});
|
|
1255
|
-
});
|
|
1256
|
-
};
|
|
1257
|
-
Doc2Vec.prototype.removeObsoleteChunksSQLite = function (db, visitedUrls, urlPrefix, logger) {
|
|
1258
|
-
var getChunksForUrlStmt = db.prepare("\n SELECT chunk_id, url FROM vec_items\n WHERE url LIKE ? || '%'\n ");
|
|
1259
|
-
var deleteChunkStmt = db.prepare("DELETE FROM vec_items WHERE chunk_id = ?");
|
|
1260
|
-
var existingChunks = getChunksForUrlStmt.all(urlPrefix);
|
|
1261
|
-
var deletedCount = 0;
|
|
1262
|
-
var transaction = db.transaction(function () {
|
|
1263
|
-
for (var _i = 0, existingChunks_1 = existingChunks; _i < existingChunks_1.length; _i++) {
|
|
1264
|
-
var _a = existingChunks_1[_i], chunk_id = _a.chunk_id, url = _a.url;
|
|
1265
|
-
if (!visitedUrls.has(url)) {
|
|
1266
|
-
logger.debug("Deleting obsolete chunk from SQLite: ".concat(chunk_id.substring(0, 8), "... (URL not visited)"));
|
|
1267
|
-
deleteChunkStmt.run(chunk_id);
|
|
1268
|
-
deletedCount++;
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
});
|
|
1272
|
-
transaction();
|
|
1273
|
-
logger.info("Deleted ".concat(deletedCount, " obsolete chunks from SQLite for URL ").concat(urlPrefix));
|
|
1274
|
-
};
|
|
1275
|
-
Doc2Vec.prototype.removeObsoleteFilesSQLite = function (db, processedFiles, dirPrefix, logger) {
|
|
1276
|
-
var getChunksForDirStmt = db.prepare("\n SELECT chunk_id, url FROM vec_items\n WHERE url LIKE 'file://%' AND url LIKE ?\n ");
|
|
1277
|
-
var deleteChunkStmt = db.prepare("DELETE FROM vec_items WHERE chunk_id = ?");
|
|
1278
|
-
var cleanedDirPrefix = dirPrefix.replace(/^\.\/+/, '');
|
|
1279
|
-
var filePrefix = "file://".concat(cleanedDirPrefix);
|
|
1280
|
-
var existingChunks = getChunksForDirStmt.all("".concat(filePrefix, "%"));
|
|
1281
|
-
var deletedCount = 0;
|
|
1282
|
-
var transaction = db.transaction(function () {
|
|
1283
|
-
for (var _i = 0, existingChunks_2 = existingChunks; _i < existingChunks_2.length; _i++) {
|
|
1284
|
-
var _a = existingChunks_2[_i], chunk_id = _a.chunk_id, url = _a.url;
|
|
1285
|
-
// Remove file:// prefix to match with processedFiles
|
|
1286
|
-
var filePath = url.substring(7);
|
|
1287
|
-
if (!processedFiles.has(filePath)) {
|
|
1288
|
-
logger.debug("Deleting obsolete chunk from SQLite: ".concat(chunk_id.substring(0, 8), "... (File not processed)"));
|
|
1289
|
-
deleteChunkStmt.run(chunk_id);
|
|
1290
|
-
deletedCount++;
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
});
|
|
1294
|
-
transaction();
|
|
1295
|
-
logger.info("Deleted ".concat(deletedCount, " obsolete chunks from SQLite for directory ").concat(dirPrefix));
|
|
1296
|
-
};
|
|
1297
|
-
Doc2Vec.prototype.removeObsoleteFilesQdrant = function (db, processedFiles, dirPrefix, logger) {
|
|
1298
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1299
|
-
var client, collectionName, cleanedDirPrefix, filePrefix, response, obsoletePointIds, error_14;
|
|
1300
|
-
return __generator(this, function (_a) {
|
|
1301
|
-
switch (_a.label) {
|
|
1302
|
-
case 0:
|
|
1303
|
-
client = db.client, collectionName = db.collectionName;
|
|
1304
|
-
_a.label = 1;
|
|
1305
|
-
case 1:
|
|
1306
|
-
_a.trys.push([1, 6, , 7]);
|
|
1307
|
-
cleanedDirPrefix = dirPrefix.replace(/^\.\/+/, '');
|
|
1308
|
-
filePrefix = "file://".concat(cleanedDirPrefix);
|
|
1309
|
-
return [4 /*yield*/, client.scroll(collectionName, {
|
|
1310
|
-
limit: 10000,
|
|
1311
|
-
with_payload: true,
|
|
1312
|
-
with_vector: false,
|
|
1313
|
-
filter: {
|
|
1314
|
-
must: [
|
|
1315
|
-
{
|
|
1316
|
-
key: "url",
|
|
1317
|
-
match: {
|
|
1318
|
-
text: "".concat(filePrefix)
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
],
|
|
1322
|
-
must_not: [
|
|
1323
|
-
{
|
|
1324
|
-
key: "is_metadata",
|
|
1325
|
-
match: {
|
|
1326
|
-
value: true
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
]
|
|
1330
|
-
}
|
|
1331
|
-
})];
|
|
1332
|
-
case 2:
|
|
1333
|
-
response = _a.sent();
|
|
1334
|
-
obsoletePointIds = response.points
|
|
1335
|
-
.filter(function (point) {
|
|
1336
|
-
var _a, _b;
|
|
1337
|
-
var url = (_a = point.payload) === null || _a === void 0 ? void 0 : _a.url;
|
|
1338
|
-
// Double check it's not a metadata record
|
|
1339
|
-
if (((_b = point.payload) === null || _b === void 0 ? void 0 : _b.is_metadata) === true) {
|
|
1340
|
-
return false;
|
|
1341
|
-
}
|
|
1342
|
-
// Remove file:// prefix to match with processedFiles
|
|
1343
|
-
var filePath = url && url.startsWith('file://') ? url.substring(7) : '';
|
|
1344
|
-
return filePath && !processedFiles.has(filePath);
|
|
1345
|
-
})
|
|
1346
|
-
.map(function (point) { return point.id; });
|
|
1347
|
-
if (!(obsoletePointIds.length > 0)) return [3 /*break*/, 4];
|
|
1348
|
-
return [4 /*yield*/, client.delete(collectionName, {
|
|
1349
|
-
points: obsoletePointIds,
|
|
1350
|
-
})];
|
|
1351
|
-
case 3:
|
|
1352
|
-
_a.sent();
|
|
1353
|
-
logger.info("Deleted ".concat(obsoletePointIds.length, " obsolete chunks from Qdrant for directory ").concat(dirPrefix));
|
|
1354
|
-
return [3 /*break*/, 5];
|
|
1355
|
-
case 4:
|
|
1356
|
-
logger.info("No obsolete chunks to delete from Qdrant for directory ".concat(dirPrefix));
|
|
1357
|
-
_a.label = 5;
|
|
1358
|
-
case 5: return [3 /*break*/, 7];
|
|
1359
|
-
case 6:
|
|
1360
|
-
error_14 = _a.sent();
|
|
1361
|
-
logger.error("Error removing obsolete chunks from Qdrant:", error_14);
|
|
1362
|
-
return [3 /*break*/, 7];
|
|
1363
|
-
case 7: return [2 /*return*/];
|
|
1364
|
-
}
|
|
1365
|
-
});
|
|
1366
|
-
});
|
|
1367
|
-
};
|
|
1368
|
-
Doc2Vec.prototype.isValidUuid = function (str) {
|
|
1369
|
-
var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
1370
|
-
return uuidRegex.test(str);
|
|
1371
|
-
};
|
|
1372
|
-
Doc2Vec.prototype.hashToUuid = function (hash) {
|
|
1373
|
-
var truncatedHash = hash.substring(0, 32);
|
|
1374
|
-
return [
|
|
1375
|
-
truncatedHash.substring(0, 8),
|
|
1376
|
-
truncatedHash.substring(8, 12),
|
|
1377
|
-
'5' + truncatedHash.substring(13, 16),
|
|
1378
|
-
'8' + truncatedHash.substring(17, 20),
|
|
1379
|
-
truncatedHash.substring(20, 32)
|
|
1380
|
-
].join('-');
|
|
1381
|
-
};
|
|
1382
|
-
Doc2Vec.prototype.removeObsoleteChunksQdrant = function (db, visitedUrls, urlPrefix, logger) {
|
|
1383
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1384
|
-
var client, collectionName, response, obsoletePointIds, error_15;
|
|
1385
|
-
return __generator(this, function (_a) {
|
|
1386
|
-
switch (_a.label) {
|
|
1387
|
-
case 0:
|
|
1388
|
-
client = db.client, collectionName = db.collectionName;
|
|
1389
|
-
_a.label = 1;
|
|
1390
|
-
case 1:
|
|
1391
|
-
_a.trys.push([1, 6, , 7]);
|
|
1392
|
-
return [4 /*yield*/, client.scroll(collectionName, {
|
|
1393
|
-
limit: 10000,
|
|
1394
|
-
with_payload: true,
|
|
1395
|
-
with_vector: false,
|
|
1396
|
-
filter: {
|
|
1397
|
-
must: [
|
|
1398
|
-
{
|
|
1399
|
-
key: "url",
|
|
1400
|
-
match: {
|
|
1401
|
-
text: urlPrefix
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
],
|
|
1405
|
-
must_not: [
|
|
1406
|
-
{
|
|
1407
|
-
key: "is_metadata",
|
|
1408
|
-
match: {
|
|
1409
|
-
value: true
|
|
1410
|
-
}
|
|
1411
|
-
}
|
|
1412
|
-
]
|
|
1413
|
-
}
|
|
1414
|
-
})];
|
|
1415
|
-
case 2:
|
|
1416
|
-
response = _a.sent();
|
|
1417
|
-
obsoletePointIds = response.points
|
|
1418
|
-
.filter(function (point) {
|
|
1419
|
-
var _a, _b;
|
|
1420
|
-
var url = (_a = point.payload) === null || _a === void 0 ? void 0 : _a.url;
|
|
1421
|
-
// Double check it's not a metadata record
|
|
1422
|
-
if (((_b = point.payload) === null || _b === void 0 ? void 0 : _b.is_metadata) === true) {
|
|
1423
|
-
return false;
|
|
1424
|
-
}
|
|
1425
|
-
return url && !visitedUrls.has(url);
|
|
1426
|
-
})
|
|
1427
|
-
.map(function (point) { return point.id; });
|
|
1428
|
-
if (!(obsoletePointIds.length > 0)) return [3 /*break*/, 4];
|
|
1429
|
-
return [4 /*yield*/, client.delete(collectionName, {
|
|
1430
|
-
points: obsoletePointIds,
|
|
1431
|
-
})];
|
|
1432
|
-
case 3:
|
|
1433
|
-
_a.sent();
|
|
1434
|
-
logger.info("Deleted ".concat(obsoletePointIds.length, " obsolete chunks from Qdrant for URL ").concat(urlPrefix));
|
|
1435
|
-
return [3 /*break*/, 5];
|
|
1436
|
-
case 4:
|
|
1437
|
-
logger.info("No obsolete chunks to delete from Qdrant for URL ".concat(urlPrefix));
|
|
1438
|
-
_a.label = 5;
|
|
1439
|
-
case 5: return [3 /*break*/, 7];
|
|
1440
|
-
case 6:
|
|
1441
|
-
error_15 = _a.sent();
|
|
1442
|
-
logger.error("Error removing obsolete chunks from Qdrant:", error_15);
|
|
1443
|
-
return [3 /*break*/, 7];
|
|
1444
|
-
case 7: return [2 /*return*/];
|
|
1445
|
-
}
|
|
1446
|
-
});
|
|
1447
|
-
});
|
|
1448
|
-
};
|
|
1449
|
-
Doc2Vec.prototype.crawlWebsite = function (baseUrl, sourceConfig, processPageContent, parentLogger, visitedUrls) {
|
|
1450
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1451
|
-
var logger, queue, processedCount, skippedCount, skippedSizeCount, errorCount, _loop_2, this_1;
|
|
1452
|
-
var _this = this;
|
|
1453
|
-
return __generator(this, function (_a) {
|
|
1454
|
-
switch (_a.label) {
|
|
1455
|
-
case 0:
|
|
1456
|
-
logger = parentLogger.child('crawler');
|
|
1457
|
-
queue = [baseUrl];
|
|
1458
|
-
logger.info("Starting crawl from ".concat(baseUrl));
|
|
1459
|
-
processedCount = 0;
|
|
1460
|
-
skippedCount = 0;
|
|
1461
|
-
skippedSizeCount = 0;
|
|
1462
|
-
errorCount = 0;
|
|
1463
|
-
_loop_2 = function () {
|
|
1464
|
-
var url, normalizedUrl, content, response, $_1, newLinksFound_1, error_16;
|
|
1465
|
-
return __generator(this, function (_b) {
|
|
1466
|
-
switch (_b.label) {
|
|
1467
|
-
case 0:
|
|
1468
|
-
url = queue.shift();
|
|
1469
|
-
if (!url)
|
|
1470
|
-
return [2 /*return*/, "continue"];
|
|
1471
|
-
normalizedUrl = this_1.normalizeUrl(url);
|
|
1472
|
-
if (visitedUrls.has(normalizedUrl))
|
|
1473
|
-
return [2 /*return*/, "continue"];
|
|
1474
|
-
visitedUrls.add(normalizedUrl);
|
|
1475
|
-
if (!this_1.shouldProcessUrl(url)) {
|
|
1476
|
-
logger.debug("Skipping URL with unsupported extension: ".concat(url));
|
|
1477
|
-
skippedCount++;
|
|
1478
|
-
return [2 /*return*/, "continue"];
|
|
1479
|
-
}
|
|
1480
|
-
_b.label = 1;
|
|
1481
|
-
case 1:
|
|
1482
|
-
_b.trys.push([1, 7, , 8]);
|
|
1483
|
-
logger.info("Crawling: ".concat(url));
|
|
1484
|
-
return [4 /*yield*/, this_1.processPage(url, sourceConfig)];
|
|
1485
|
-
case 2:
|
|
1486
|
-
content = _b.sent();
|
|
1487
|
-
if (!(content !== null)) return [3 /*break*/, 4];
|
|
1488
|
-
return [4 /*yield*/, processPageContent(url, content)];
|
|
1489
|
-
case 3:
|
|
1490
|
-
_b.sent();
|
|
1491
|
-
processedCount++;
|
|
1492
|
-
return [3 /*break*/, 5];
|
|
1493
|
-
case 4:
|
|
1494
|
-
skippedSizeCount++;
|
|
1495
|
-
_b.label = 5;
|
|
1496
|
-
case 5: return [4 /*yield*/, axios_1.default.get(url)];
|
|
1497
|
-
case 6:
|
|
1498
|
-
response = _b.sent();
|
|
1499
|
-
$_1 = (0, cheerio_1.load)(response.data);
|
|
1500
|
-
logger.debug("Finding links on page ".concat(url));
|
|
1501
|
-
newLinksFound_1 = 0;
|
|
1502
|
-
$_1('a[href]').each(function (_, element) {
|
|
1503
|
-
var href = $_1(element).attr('href');
|
|
1504
|
-
if (!href || href.startsWith('#') || href.startsWith('mailto:'))
|
|
1505
|
-
return;
|
|
1506
|
-
var fullUrl = _this.buildUrl(href, url);
|
|
1507
|
-
if (fullUrl.startsWith(sourceConfig.url) && !visitedUrls.has(_this.normalizeUrl(fullUrl))) {
|
|
1508
|
-
if (!queue.includes(fullUrl)) {
|
|
1509
|
-
queue.push(fullUrl);
|
|
1510
|
-
newLinksFound_1++;
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
});
|
|
1514
|
-
logger.debug("Found ".concat(newLinksFound_1, " new links on ").concat(url));
|
|
1515
|
-
return [3 /*break*/, 8];
|
|
1516
|
-
case 7:
|
|
1517
|
-
error_16 = _b.sent();
|
|
1518
|
-
logger.error("Failed during link discovery or initial fetch for ".concat(url, ":"), error_16);
|
|
1519
|
-
errorCount++;
|
|
1520
|
-
return [3 /*break*/, 8];
|
|
1521
|
-
case 8: return [2 /*return*/];
|
|
1522
|
-
}
|
|
1523
|
-
});
|
|
1524
|
-
};
|
|
1525
|
-
this_1 = this;
|
|
1526
|
-
_a.label = 1;
|
|
1527
|
-
case 1:
|
|
1528
|
-
if (!(queue.length > 0)) return [3 /*break*/, 3];
|
|
1529
|
-
return [5 /*yield**/, _loop_2()];
|
|
1530
|
-
case 2:
|
|
1531
|
-
_a.sent();
|
|
1532
|
-
return [3 /*break*/, 1];
|
|
1533
|
-
case 3:
|
|
1534
|
-
logger.info("Crawl completed. Processed: ".concat(processedCount, ", Skipped (Extension): ").concat(skippedCount, ", Skipped (Size): ").concat(skippedSizeCount, ", Errors: ").concat(errorCount));
|
|
1535
|
-
return [2 /*return*/];
|
|
1536
|
-
}
|
|
1537
|
-
});
|
|
1538
|
-
});
|
|
1539
|
-
};
|
|
1540
|
-
Doc2Vec.prototype.shouldProcessUrl = function (url) {
|
|
1541
|
-
var parsedUrl = new URL(url);
|
|
1542
|
-
var pathname = parsedUrl.pathname;
|
|
1543
|
-
var ext = path.extname(pathname);
|
|
1544
|
-
if (!ext)
|
|
1545
|
-
return true;
|
|
1546
|
-
return ['.html', '.htm'].includes(ext.toLowerCase());
|
|
1547
|
-
};
|
|
1548
|
-
Doc2Vec.prototype.normalizeUrl = function (url) {
|
|
1549
|
-
try {
|
|
1550
|
-
var urlObj = new URL(url);
|
|
1551
|
-
urlObj.hash = '';
|
|
1552
|
-
urlObj.search = '';
|
|
1553
|
-
return urlObj.toString();
|
|
1554
|
-
}
|
|
1555
|
-
catch (error) {
|
|
1556
|
-
return url;
|
|
1557
|
-
}
|
|
1558
|
-
};
|
|
1559
|
-
Doc2Vec.prototype.buildUrl = function (href, currentUrl) {
|
|
1560
|
-
try {
|
|
1561
|
-
return new URL(href, currentUrl).toString();
|
|
1562
|
-
}
|
|
1563
|
-
catch (error) {
|
|
1564
|
-
this.logger.warn("Invalid URL found: ".concat(href));
|
|
1565
|
-
return '';
|
|
1566
|
-
}
|
|
1567
|
-
};
|
|
1568
|
-
Doc2Vec.prototype.processPage = function (url, sourceConfig) {
|
|
1569
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1570
|
-
var logger, browser, page, htmlContent, dom, document_1, reader, article, cleanHtml, markdown, error_17;
|
|
1571
|
-
var _this = this;
|
|
1572
|
-
return __generator(this, function (_a) {
|
|
1573
|
-
switch (_a.label) {
|
|
1574
|
-
case 0:
|
|
1575
|
-
logger = this.logger.child('page-processor');
|
|
1576
|
-
logger.debug("Processing page content from ".concat(url));
|
|
1577
|
-
return [4 /*yield*/, puppeteer_1.default.launch({
|
|
1578
|
-
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
|
1579
|
-
})];
|
|
1580
|
-
case 1:
|
|
1581
|
-
browser = _a.sent();
|
|
1582
|
-
_a.label = 2;
|
|
1583
|
-
case 2:
|
|
1584
|
-
_a.trys.push([2, 10, 11, 14]);
|
|
1585
|
-
return [4 /*yield*/, browser.newPage()];
|
|
1586
|
-
case 3:
|
|
1587
|
-
page = _a.sent();
|
|
1588
|
-
logger.debug("Navigating to ".concat(url));
|
|
1589
|
-
return [4 /*yield*/, page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 })];
|
|
1590
|
-
case 4:
|
|
1591
|
-
_a.sent();
|
|
1592
|
-
return [4 /*yield*/, page.evaluate(function () {
|
|
1593
|
-
var mainContentElement = document_1.querySelector('div[role="main"].document') || document_1.querySelector('main') || document_1.body;
|
|
1594
|
-
return mainContentElement.innerHTML;
|
|
1595
|
-
})];
|
|
1596
|
-
case 5:
|
|
1597
|
-
htmlContent = _a.sent();
|
|
1598
|
-
if (!(htmlContent.length > sourceConfig.max_size)) return [3 /*break*/, 7];
|
|
1599
|
-
logger.warn("Raw HTML content (".concat(htmlContent.length, " chars) exceeds max size (").concat(sourceConfig.max_size, "). Skipping detailed processing for ").concat(url, "."));
|
|
1600
|
-
return [4 /*yield*/, browser.close()];
|
|
1601
|
-
case 6:
|
|
1602
|
-
_a.sent();
|
|
1603
|
-
return [2 /*return*/, null];
|
|
1604
|
-
case 7:
|
|
1605
|
-
logger.debug("Got HTML content (".concat(htmlContent.length, " chars), creating DOM"));
|
|
1606
|
-
dom = new jsdom_1.JSDOM(htmlContent);
|
|
1607
|
-
document_1 = dom.window.document;
|
|
1608
|
-
document_1.querySelectorAll('pre').forEach(function (pre) {
|
|
1609
|
-
pre.classList.add('article-content');
|
|
1610
|
-
pre.setAttribute('data-readable-content-score', '100');
|
|
1611
|
-
_this.markCodeParents(pre.parentElement);
|
|
1612
|
-
});
|
|
1613
|
-
logger.debug("Applying Readability to extract main content");
|
|
1614
|
-
reader = new readability_1.Readability(document_1, {
|
|
1615
|
-
charThreshold: 20,
|
|
1616
|
-
classesToPreserve: ['article-content'],
|
|
1617
|
-
});
|
|
1618
|
-
article = reader.parse();
|
|
1619
|
-
if (!!article) return [3 /*break*/, 9];
|
|
1620
|
-
logger.warn("Failed to parse article content with Readability for ".concat(url));
|
|
1621
|
-
return [4 /*yield*/, browser.close()];
|
|
1622
|
-
case 8:
|
|
1623
|
-
_a.sent();
|
|
1624
|
-
return [2 /*return*/, null];
|
|
1625
|
-
case 9:
|
|
1626
|
-
logger.debug("Sanitizing HTML (".concat(article.content.length, " chars)"));
|
|
1627
|
-
cleanHtml = (0, sanitize_html_1.default)(article.content, {
|
|
1628
|
-
allowedTags: [
|
|
1629
|
-
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'a', 'ul', 'ol',
|
|
1630
|
-
'li', 'b', 'i', 'strong', 'em', 'code', 'pre',
|
|
1631
|
-
'div', 'span', 'table', 'thead', 'tbody', 'tr', 'th', 'td'
|
|
1632
|
-
],
|
|
1633
|
-
allowedAttributes: {
|
|
1634
|
-
'a': ['href'],
|
|
1635
|
-
'pre': ['class', 'data-language'],
|
|
1636
|
-
'code': ['class', 'data-language'],
|
|
1637
|
-
'div': ['class'],
|
|
1638
|
-
'span': ['class']
|
|
1639
|
-
}
|
|
1640
|
-
});
|
|
1641
|
-
logger.debug("Converting HTML to Markdown");
|
|
1642
|
-
markdown = this.turndownService.turndown(cleanHtml);
|
|
1643
|
-
logger.debug("Markdown conversion complete (".concat(markdown.length, " chars)"));
|
|
1644
|
-
return [2 /*return*/, markdown];
|
|
1645
|
-
case 10:
|
|
1646
|
-
error_17 = _a.sent();
|
|
1647
|
-
logger.error("Error processing page ".concat(url, ":"), error_17);
|
|
1648
|
-
return [2 /*return*/, null];
|
|
1649
|
-
case 11:
|
|
1650
|
-
if (!(browser && browser.isConnected())) return [3 /*break*/, 13];
|
|
1651
|
-
return [4 /*yield*/, browser.close()];
|
|
1652
|
-
case 12:
|
|
1653
|
-
_a.sent();
|
|
1654
|
-
logger.debug("Browser closed for ".concat(url));
|
|
1655
|
-
_a.label = 13;
|
|
1656
|
-
case 13: return [7 /*endfinally*/];
|
|
1657
|
-
case 14: return [2 /*return*/];
|
|
1658
|
-
}
|
|
1659
|
-
});
|
|
1660
|
-
});
|
|
1661
|
-
};
|
|
1662
|
-
Doc2Vec.prototype.markCodeParents = function (node) {
|
|
1663
|
-
if (!node)
|
|
1664
|
-
return;
|
|
1665
|
-
if (node.querySelector('pre, code')) {
|
|
1666
|
-
node.classList.add('article-content');
|
|
1667
|
-
node.setAttribute('data-readable-content-score', '100');
|
|
1668
|
-
}
|
|
1669
|
-
this.markCodeParents(node.parentElement);
|
|
1670
|
-
};
|
|
1671
|
-
Doc2Vec.prototype.chunkMarkdown = function (markdown, sourceConfig, url) {
|
|
1672
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1673
|
-
var logger, MAX_TOKENS, chunks, lines, currentChunk, headingHierarchy, processChunk, createDocumentChunk, _i, lines_2, line, levelMatch, level, heading;
|
|
1674
|
-
var _this = this;
|
|
1675
|
-
return __generator(this, function (_a) {
|
|
1676
|
-
logger = this.logger.child('chunker');
|
|
1677
|
-
logger.debug("Chunking markdown from ".concat(url, " (").concat(markdown.length, " chars)"));
|
|
1678
|
-
MAX_TOKENS = 1000;
|
|
1679
|
-
chunks = [];
|
|
1680
|
-
lines = markdown.split("\n");
|
|
1681
|
-
currentChunk = "";
|
|
1682
|
-
headingHierarchy = [];
|
|
1683
|
-
processChunk = function () {
|
|
1684
|
-
if (currentChunk.trim()) {
|
|
1685
|
-
var tokens = _this.tokenize(currentChunk);
|
|
1686
|
-
if (tokens.length > MAX_TOKENS) {
|
|
1687
|
-
logger.debug("Chunk exceeds max token count (".concat(tokens.length, "), splitting into smaller chunks"));
|
|
1688
|
-
var subChunk = "";
|
|
1689
|
-
var tokenCount = 0;
|
|
1690
|
-
var overlapSize = Math.floor(MAX_TOKENS * 0.05);
|
|
1691
|
-
var lastTokens = [];
|
|
1692
|
-
for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) {
|
|
1693
|
-
var token = tokens_1[_i];
|
|
1694
|
-
if (tokenCount + 1 > MAX_TOKENS) {
|
|
1695
|
-
chunks.push(createDocumentChunk(subChunk, headingHierarchy));
|
|
1696
|
-
subChunk = lastTokens.join("") + token;
|
|
1697
|
-
tokenCount = lastTokens.length + 1;
|
|
1698
|
-
lastTokens = [];
|
|
1699
|
-
}
|
|
1700
|
-
else {
|
|
1701
|
-
subChunk += token;
|
|
1702
|
-
tokenCount++;
|
|
1703
|
-
lastTokens.push(token);
|
|
1704
|
-
if (lastTokens.length > overlapSize) {
|
|
1705
|
-
lastTokens.shift();
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
}
|
|
1709
|
-
if (subChunk) {
|
|
1710
|
-
chunks.push(createDocumentChunk(subChunk, headingHierarchy));
|
|
1711
|
-
}
|
|
1712
|
-
}
|
|
1713
|
-
else {
|
|
1714
|
-
chunks.push(createDocumentChunk(currentChunk, headingHierarchy));
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
currentChunk = "";
|
|
1718
|
-
};
|
|
1719
|
-
createDocumentChunk = function (content, hierarchy) {
|
|
1720
|
-
var chunkId = _this.generateHash(content);
|
|
1721
|
-
logger.debug("Created chunk ".concat(chunkId.substring(0, 8), "... with ").concat(content.length, " chars"));
|
|
1722
|
-
return {
|
|
1723
|
-
content: content,
|
|
1724
|
-
metadata: {
|
|
1725
|
-
product_name: sourceConfig.product_name,
|
|
1726
|
-
version: sourceConfig.version,
|
|
1727
|
-
heading_hierarchy: __spreadArray([], hierarchy, true),
|
|
1728
|
-
section: hierarchy[hierarchy.length - 1] || "Introduction",
|
|
1729
|
-
chunk_id: chunkId,
|
|
1730
|
-
url: url,
|
|
1731
|
-
hash: _this.generateHash(content)
|
|
1732
|
-
}
|
|
1733
|
-
};
|
|
1734
|
-
};
|
|
1735
|
-
for (_i = 0, lines_2 = lines; _i < lines_2.length; _i++) {
|
|
1736
|
-
line = lines_2[_i];
|
|
1737
|
-
if (line.startsWith("#")) {
|
|
1738
|
-
processChunk();
|
|
1739
|
-
levelMatch = line.match(/^(#+)/);
|
|
1740
|
-
level = levelMatch ? levelMatch[1].length : 1;
|
|
1741
|
-
heading = line.replace(/^#+\s*/, "").trim();
|
|
1742
|
-
logger.debug("Found heading (level ".concat(level, "): ").concat(heading));
|
|
1743
|
-
while (headingHierarchy.length < level - 1) {
|
|
1744
|
-
headingHierarchy.push("");
|
|
1745
|
-
}
|
|
1746
|
-
if (level <= headingHierarchy.length) {
|
|
1747
|
-
headingHierarchy = headingHierarchy.slice(0, level - 1);
|
|
1748
|
-
}
|
|
1749
|
-
headingHierarchy[level - 1] = heading;
|
|
1750
|
-
}
|
|
1751
|
-
else {
|
|
1752
|
-
currentChunk += "".concat(line, "\n");
|
|
1753
|
-
}
|
|
1754
|
-
}
|
|
1755
|
-
processChunk();
|
|
1756
|
-
logger.debug("Chunking complete, created ".concat(chunks.length, " chunks"));
|
|
1757
|
-
return [2 /*return*/, chunks];
|
|
1758
|
-
});
|
|
1759
|
-
});
|
|
1760
|
-
};
|
|
1761
|
-
Doc2Vec.prototype.tokenize = function (text) {
|
|
1762
|
-
return text.split(/(\s+)/).filter(function (token) { return token.length > 0; });
|
|
1763
|
-
};
|
|
1764
|
-
Doc2Vec.prototype.createEmbeddings = function (texts) {
|
|
1765
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1766
|
-
var logger, response, error_18;
|
|
1767
|
-
return __generator(this, function (_a) {
|
|
1768
|
-
switch (_a.label) {
|
|
1769
|
-
case 0:
|
|
1770
|
-
logger = this.logger.child('embeddings');
|
|
1771
|
-
_a.label = 1;
|
|
1772
|
-
case 1:
|
|
1773
|
-
_a.trys.push([1, 3, , 4]);
|
|
1774
|
-
logger.debug("Creating embeddings for ".concat(texts.length, " texts"));
|
|
1775
|
-
return [4 /*yield*/, this.openai.embeddings.create({
|
|
1776
|
-
model: "text-embedding-3-large",
|
|
1777
|
-
input: texts,
|
|
1778
|
-
})];
|
|
1779
|
-
case 2:
|
|
1780
|
-
response = _a.sent();
|
|
1781
|
-
logger.debug("Successfully created ".concat(response.data.length, " embeddings"));
|
|
1782
|
-
return [2 /*return*/, response.data.map(function (d) { return d.embedding; })];
|
|
1783
|
-
case 3:
|
|
1784
|
-
error_18 = _a.sent();
|
|
1785
|
-
logger.error('Failed to create embeddings:', error_18);
|
|
1786
|
-
return [2 /*return*/, []];
|
|
1787
|
-
case 4: return [2 /*return*/];
|
|
1788
|
-
}
|
|
1789
|
-
});
|
|
1790
|
-
});
|
|
1791
|
-
};
|
|
1792
|
-
Doc2Vec.prototype.generateHash = function (content) {
|
|
1793
|
-
return crypto_1.default.createHash("sha256").update(content).digest("hex");
|
|
1794
|
-
};
|
|
1795
|
-
return Doc2Vec;
|
|
1796
|
-
}());
|
|
1797
|
-
if (require.main === module) {
|
|
1798
|
-
var configPath = process.argv[2] || 'config.yaml';
|
|
1799
|
-
if (!fs.existsSync(configPath)) {
|
|
1800
|
-
console.error('Please provide a valid path to a YAML config file.');
|
|
1801
|
-
process.exit(1);
|
|
1802
|
-
}
|
|
1803
|
-
var doc2Vec = new Doc2Vec(configPath);
|
|
1804
|
-
doc2Vec.run().catch(console.error);
|
|
1805
|
-
}
|