doc2vec 1.3.0 → 2.2.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/README.md +216 -8
- package/dist/code-chunker.js +179 -0
- package/dist/content-processor copy.js +819 -0
- package/dist/content-processor.js +620 -72
- package/dist/database.js +205 -19
- package/dist/doc2vec.js +520 -8
- package/dist/electron-app/src/database.js +218 -0
- package/dist/electron-app/src/embeddings.js +80 -0
- package/dist/electron-app/src/main.js +627 -0
- package/dist/electron-app/src/mcp-server.js +496 -0
- package/dist/electron-app/src/preload.js +24 -0
- package/dist/electron-app/src/processor.js +248 -0
- package/dist/electron-app/src/syncer.js +146 -0
- package/dist/macos-app/migrate-to-vec0.js +156 -0
- package/dist/vitest.config.js +16 -0
- package/package.json +13 -3
- package/dist/embedding-providers.js +0 -157
|
@@ -0,0 +1,248 @@
|
|
|
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
|
+
exports.ContentProcessor = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const crypto = __importStar(require("crypto"));
|
|
40
|
+
class ContentProcessor {
|
|
41
|
+
constructor() {
|
|
42
|
+
this.maxTokens = 1000;
|
|
43
|
+
this.minTokens = 150;
|
|
44
|
+
this.overlapPercent = 0.1;
|
|
45
|
+
}
|
|
46
|
+
async readFile(filePath) {
|
|
47
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
48
|
+
try {
|
|
49
|
+
switch (ext) {
|
|
50
|
+
case '.pdf':
|
|
51
|
+
return await this.readPdf(filePath);
|
|
52
|
+
case '.doc':
|
|
53
|
+
return await this.readDoc(filePath);
|
|
54
|
+
case '.docx':
|
|
55
|
+
return await this.readDocx(filePath);
|
|
56
|
+
case '.html':
|
|
57
|
+
case '.htm':
|
|
58
|
+
const html = fs.readFileSync(filePath, 'utf-8');
|
|
59
|
+
return this.stripHtml(html);
|
|
60
|
+
default:
|
|
61
|
+
return fs.readFileSync(filePath, 'utf-8');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.error(`Error reading file ${filePath}:`, error);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async readPdf(filePath) {
|
|
70
|
+
try {
|
|
71
|
+
const pdfParse = require('pdf-parse');
|
|
72
|
+
const buffer = fs.readFileSync(filePath);
|
|
73
|
+
const data = await pdfParse(buffer);
|
|
74
|
+
return `# ${path.basename(filePath, '.pdf')}\n\n${data.text}`;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error.code === 'MODULE_NOT_FOUND') {
|
|
78
|
+
console.warn(`PDF parsing not available for ${filePath}. Install pdf-parse for PDF support.`);
|
|
79
|
+
return `# ${path.basename(filePath, '.pdf')}\n\n[PDF content - install pdf-parse for extraction]`;
|
|
80
|
+
}
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async readDoc(filePath) {
|
|
85
|
+
try {
|
|
86
|
+
const WordExtractor = require('word-extractor');
|
|
87
|
+
const extractor = new WordExtractor();
|
|
88
|
+
const extracted = await extractor.extract(filePath);
|
|
89
|
+
const text = extracted.getBody();
|
|
90
|
+
// Create content with filename as title
|
|
91
|
+
let content = `# ${path.basename(filePath, '.doc')}\n\n`;
|
|
92
|
+
// Clean up the text
|
|
93
|
+
const cleanedText = text
|
|
94
|
+
.replace(/\r\n/g, '\n')
|
|
95
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
96
|
+
.trim();
|
|
97
|
+
content += cleanedText;
|
|
98
|
+
return content;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (error.code === 'MODULE_NOT_FOUND') {
|
|
102
|
+
console.warn(`DOC parsing not available for ${filePath}. Install word-extractor for DOC support.`);
|
|
103
|
+
return `# ${path.basename(filePath, '.doc')}\n\n[DOC content - install word-extractor for extraction]`;
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async readDocx(filePath) {
|
|
109
|
+
try {
|
|
110
|
+
const mammoth = require('mammoth');
|
|
111
|
+
const result = await mammoth.convertToHtml({ path: filePath });
|
|
112
|
+
const html = result.value;
|
|
113
|
+
// Log any warnings
|
|
114
|
+
if (result.messages.length > 0) {
|
|
115
|
+
console.log(`Mammoth warnings for ${filePath}: ${result.messages.map((m) => m.message).join(', ')}`);
|
|
116
|
+
}
|
|
117
|
+
// Create content with filename as title
|
|
118
|
+
let content = `# ${path.basename(filePath, '.docx')}\n\n`;
|
|
119
|
+
// Strip HTML to get plain text
|
|
120
|
+
content += this.stripHtml(html);
|
|
121
|
+
// Clean up excessive line breaks
|
|
122
|
+
content = content.replace(/\n{3,}/g, '\n\n').trim();
|
|
123
|
+
return content;
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (error.code === 'MODULE_NOT_FOUND') {
|
|
127
|
+
console.warn(`DOCX parsing not available for ${filePath}. Install mammoth for DOCX support.`);
|
|
128
|
+
return `# ${path.basename(filePath, '.docx')}\n\n[DOCX content - install mammoth for extraction]`;
|
|
129
|
+
}
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
stripHtml(html) {
|
|
134
|
+
return html
|
|
135
|
+
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
|
136
|
+
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
|
|
137
|
+
.replace(/<[^>]+>/g, '')
|
|
138
|
+
.replace(/ /g, ' ')
|
|
139
|
+
.replace(/&/g, '&')
|
|
140
|
+
.replace(/</g, '<')
|
|
141
|
+
.replace(/>/g, '>')
|
|
142
|
+
.replace(/"/g, '"')
|
|
143
|
+
.replace(/\s+/g, ' ')
|
|
144
|
+
.trim();
|
|
145
|
+
}
|
|
146
|
+
generateHash(content) {
|
|
147
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
148
|
+
}
|
|
149
|
+
tokenize(text) {
|
|
150
|
+
return text.split(/(\s+)/).filter(token => token.length > 0);
|
|
151
|
+
}
|
|
152
|
+
chunkContent(content, filePath, version) {
|
|
153
|
+
const chunks = [];
|
|
154
|
+
const lines = content.split('\n');
|
|
155
|
+
let buffer = '';
|
|
156
|
+
let headingHierarchy = [];
|
|
157
|
+
let bufferHeadings = [];
|
|
158
|
+
let chunkCounter = 0;
|
|
159
|
+
const computeTopicHierarchy = () => {
|
|
160
|
+
if (bufferHeadings.length === 0)
|
|
161
|
+
return headingHierarchy;
|
|
162
|
+
const deepestLevel = Math.max(...bufferHeadings.map(h => h.level));
|
|
163
|
+
const deepestHeadings = bufferHeadings.filter(h => h.level === deepestLevel);
|
|
164
|
+
if (deepestHeadings.length > 1 && deepestLevel > 1) {
|
|
165
|
+
return headingHierarchy.slice(0, deepestLevel - 1);
|
|
166
|
+
}
|
|
167
|
+
return headingHierarchy;
|
|
168
|
+
};
|
|
169
|
+
const createChunk = (content, hierarchy) => {
|
|
170
|
+
const breadcrumbs = hierarchy.filter(h => h).join(' > ');
|
|
171
|
+
const contextPrefix = breadcrumbs ? `[Topic: ${breadcrumbs}]\n` : '';
|
|
172
|
+
const searchableText = contextPrefix + content.trim();
|
|
173
|
+
const chunkId = this.generateHash(searchableText);
|
|
174
|
+
return {
|
|
175
|
+
chunkId,
|
|
176
|
+
content: searchableText,
|
|
177
|
+
version,
|
|
178
|
+
section: hierarchy[hierarchy.length - 1] || 'Introduction',
|
|
179
|
+
headingHierarchy: hierarchy.filter(h => h),
|
|
180
|
+
chunkIndex: chunkCounter++,
|
|
181
|
+
totalChunks: 0,
|
|
182
|
+
url: `file://${filePath}`,
|
|
183
|
+
hash: chunkId
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
const flushBuffer = (force = false) => {
|
|
187
|
+
const trimmed = buffer.trim();
|
|
188
|
+
if (!trimmed)
|
|
189
|
+
return;
|
|
190
|
+
const tokenCount = this.tokenize(trimmed).length;
|
|
191
|
+
if (tokenCount < this.minTokens && !force)
|
|
192
|
+
return;
|
|
193
|
+
const hierarchy = computeTopicHierarchy();
|
|
194
|
+
if (tokenCount > this.maxTokens) {
|
|
195
|
+
const tokens = this.tokenize(trimmed);
|
|
196
|
+
const overlapSize = Math.floor(this.maxTokens * this.overlapPercent);
|
|
197
|
+
for (let i = 0; i < tokens.length; i += (this.maxTokens - overlapSize)) {
|
|
198
|
+
const subTokens = tokens.slice(i, i + this.maxTokens);
|
|
199
|
+
chunks.push(createChunk(subTokens.join(''), hierarchy));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
chunks.push(createChunk(trimmed, hierarchy));
|
|
204
|
+
}
|
|
205
|
+
buffer = '';
|
|
206
|
+
bufferHeadings = [];
|
|
207
|
+
};
|
|
208
|
+
for (const line of lines) {
|
|
209
|
+
const isHeading = line.startsWith('#');
|
|
210
|
+
if (isHeading) {
|
|
211
|
+
const match = line.match(/^(#+)/);
|
|
212
|
+
const level = match ? match[1].length : 1;
|
|
213
|
+
const headingText = line
|
|
214
|
+
.replace(/^#+\s*/, '')
|
|
215
|
+
.replace(/\[.*?\]\(#[^)]*\)/g, '')
|
|
216
|
+
.trim();
|
|
217
|
+
const currentTokens = this.tokenize(buffer.trim()).length;
|
|
218
|
+
const hasContent = currentTokens > 0;
|
|
219
|
+
const isSmall = currentTokens < this.minTokens;
|
|
220
|
+
const deepestLevel = bufferHeadings.length > 0
|
|
221
|
+
? Math.max(...bufferHeadings.map(h => h.level))
|
|
222
|
+
: 0;
|
|
223
|
+
const shouldMerge = hasContent && isSmall && bufferHeadings.length > 0 && level >= deepestLevel;
|
|
224
|
+
if (!shouldMerge && hasContent) {
|
|
225
|
+
flushBuffer();
|
|
226
|
+
}
|
|
227
|
+
headingHierarchy = headingHierarchy.slice(0, level - 1);
|
|
228
|
+
headingHierarchy[level - 1] = headingText;
|
|
229
|
+
bufferHeadings.push({ level, text: headingText });
|
|
230
|
+
buffer += line + '\n';
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
buffer += line + '\n';
|
|
234
|
+
if (this.tokenize(buffer).length >= this.maxTokens) {
|
|
235
|
+
flushBuffer();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
flushBuffer(true);
|
|
240
|
+
// Update total chunks
|
|
241
|
+
const total = chunks.length;
|
|
242
|
+
chunks.forEach(chunk => {
|
|
243
|
+
chunk.totalChunks = total;
|
|
244
|
+
});
|
|
245
|
+
return chunks;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
exports.ContentProcessor = ContentProcessor;
|
|
@@ -0,0 +1,146 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.FolderSyncer = void 0;
|
|
40
|
+
const chokidar_1 = __importDefault(require("chokidar"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
class FolderSyncer {
|
|
44
|
+
constructor(folderPath, options) {
|
|
45
|
+
this.fsWatcher = null;
|
|
46
|
+
this._isSyncing = false;
|
|
47
|
+
this.folderPath = folderPath;
|
|
48
|
+
this.options = options;
|
|
49
|
+
}
|
|
50
|
+
get isSyncing() {
|
|
51
|
+
return this._isSyncing;
|
|
52
|
+
}
|
|
53
|
+
start() {
|
|
54
|
+
if (this._isSyncing)
|
|
55
|
+
return;
|
|
56
|
+
const globPattern = this.options.recursive
|
|
57
|
+
? path.join(this.folderPath, '**', '*')
|
|
58
|
+
: path.join(this.folderPath, '*');
|
|
59
|
+
this.fsWatcher = chokidar_1.default.watch(globPattern, {
|
|
60
|
+
ignored: /(^|[\/\\])\../, // ignore dotfiles
|
|
61
|
+
persistent: true,
|
|
62
|
+
ignoreInitial: true,
|
|
63
|
+
awaitWriteFinish: {
|
|
64
|
+
stabilityThreshold: 500,
|
|
65
|
+
pollInterval: 100
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
this.fsWatcher
|
|
69
|
+
.on('add', async (filePath) => {
|
|
70
|
+
if (this.shouldProcess(filePath)) {
|
|
71
|
+
console.log(`File added: ${filePath}`);
|
|
72
|
+
await this.options.onFileAdd(filePath);
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
.on('change', async (filePath) => {
|
|
76
|
+
if (this.shouldProcess(filePath)) {
|
|
77
|
+
console.log(`File changed: ${filePath}`);
|
|
78
|
+
await this.options.onFileChange(filePath);
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
.on('unlink', async (filePath) => {
|
|
82
|
+
// For delete events, only check extension (file no longer exists)
|
|
83
|
+
if (this.hasValidExtension(filePath)) {
|
|
84
|
+
console.log(`File deleted: ${filePath}`);
|
|
85
|
+
await this.options.onFileDelete(filePath);
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
.on('error', (error) => {
|
|
89
|
+
console.error('Syncer error:', error);
|
|
90
|
+
});
|
|
91
|
+
this._isSyncing = true;
|
|
92
|
+
console.log(`Started syncing: ${this.folderPath}`);
|
|
93
|
+
}
|
|
94
|
+
stop() {
|
|
95
|
+
if (this.fsWatcher) {
|
|
96
|
+
this.fsWatcher.close();
|
|
97
|
+
this.fsWatcher = null;
|
|
98
|
+
}
|
|
99
|
+
this._isSyncing = false;
|
|
100
|
+
console.log('Stopped syncing');
|
|
101
|
+
}
|
|
102
|
+
shouldProcess(filePath) {
|
|
103
|
+
// Check if it's a file (not directory)
|
|
104
|
+
try {
|
|
105
|
+
const stat = fs.statSync(filePath);
|
|
106
|
+
if (!stat.isFile())
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
return this.hasValidExtension(filePath);
|
|
113
|
+
}
|
|
114
|
+
hasValidExtension(filePath) {
|
|
115
|
+
// Check extension
|
|
116
|
+
if (this.options.extensions.length === 0)
|
|
117
|
+
return true;
|
|
118
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
119
|
+
return this.options.extensions.includes(ext);
|
|
120
|
+
}
|
|
121
|
+
getSyncedFiles() {
|
|
122
|
+
const files = [];
|
|
123
|
+
const walkDir = (dir) => {
|
|
124
|
+
try {
|
|
125
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
126
|
+
for (const entry of entries) {
|
|
127
|
+
if (entry.name.startsWith('.'))
|
|
128
|
+
continue;
|
|
129
|
+
const fullPath = path.join(dir, entry.name);
|
|
130
|
+
if (entry.isDirectory() && this.options.recursive) {
|
|
131
|
+
walkDir(fullPath);
|
|
132
|
+
}
|
|
133
|
+
else if (entry.isFile() && this.shouldProcess(fullPath)) {
|
|
134
|
+
files.push(fullPath);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
console.error(`Error reading directory ${dir}:`, error);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
walkDir(this.folderPath);
|
|
143
|
+
return files;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
exports.FolderSyncer = FolderSyncer;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env npx ts-node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Migration script to convert DocSync SQLite database to sqlite-vec format
|
|
5
|
+
*
|
|
6
|
+
* This script reads the chunks from the DocSync database (created by the Swift app)
|
|
7
|
+
* and migrates them to a proper vec0 virtual table that the MCP server can query.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* npx ts-node migrate-to-vec0.ts <source-db> <target-db>
|
|
11
|
+
*
|
|
12
|
+
* Example:
|
|
13
|
+
* npx ts-node migrate-to-vec0.ts ~/Documents/docsync.db ./my-docs.db
|
|
14
|
+
*/
|
|
15
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
18
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
19
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
20
|
+
}
|
|
21
|
+
Object.defineProperty(o, k2, desc);
|
|
22
|
+
}) : (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
o[k2] = m[k];
|
|
25
|
+
}));
|
|
26
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
27
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
28
|
+
}) : function(o, v) {
|
|
29
|
+
o["default"] = v;
|
|
30
|
+
});
|
|
31
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
32
|
+
var ownKeys = function(o) {
|
|
33
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
34
|
+
var ar = [];
|
|
35
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
36
|
+
return ar;
|
|
37
|
+
};
|
|
38
|
+
return ownKeys(o);
|
|
39
|
+
};
|
|
40
|
+
return function (mod) {
|
|
41
|
+
if (mod && mod.__esModule) return mod;
|
|
42
|
+
var result = {};
|
|
43
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
44
|
+
__setModuleDefault(result, mod);
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
})();
|
|
48
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
49
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
50
|
+
};
|
|
51
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
53
|
+
const sqliteVec = __importStar(require("sqlite-vec"));
|
|
54
|
+
const fs = __importStar(require("fs"));
|
|
55
|
+
function migrate(sourceDbPath, targetDbPath) {
|
|
56
|
+
console.log(`Migrating from ${sourceDbPath} to ${targetDbPath}`);
|
|
57
|
+
if (!fs.existsSync(sourceDbPath)) {
|
|
58
|
+
console.error(`Source database not found: ${sourceDbPath}`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
// Open source database (DocSync format)
|
|
62
|
+
const sourceDb = new better_sqlite3_1.default(sourceDbPath);
|
|
63
|
+
// Check if source has vec_items table
|
|
64
|
+
const tableCheck = sourceDb.prepare(`
|
|
65
|
+
SELECT name FROM sqlite_master WHERE type='table' AND name='vec_items'
|
|
66
|
+
`).get();
|
|
67
|
+
if (!tableCheck) {
|
|
68
|
+
console.error('Source database does not have vec_items table');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
// Get row count
|
|
72
|
+
const countResult = sourceDb.prepare('SELECT COUNT(*) as count FROM vec_items').get();
|
|
73
|
+
console.log(`Found ${countResult.count} chunks to migrate`);
|
|
74
|
+
if (countResult.count === 0) {
|
|
75
|
+
console.log('No chunks to migrate');
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
// Open/create target database with sqlite-vec
|
|
79
|
+
const targetDb = new better_sqlite3_1.default(targetDbPath);
|
|
80
|
+
sqliteVec.load(targetDb);
|
|
81
|
+
// Create vec0 virtual table
|
|
82
|
+
targetDb.exec(`
|
|
83
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS vec_items USING vec0(
|
|
84
|
+
embedding float[3072],
|
|
85
|
+
product_name text,
|
|
86
|
+
version text,
|
|
87
|
+
heading_hierarchy text,
|
|
88
|
+
section text,
|
|
89
|
+
chunk_id text primary key,
|
|
90
|
+
content text,
|
|
91
|
+
url text,
|
|
92
|
+
hash text,
|
|
93
|
+
chunk_index integer,
|
|
94
|
+
total_chunks integer
|
|
95
|
+
)
|
|
96
|
+
`);
|
|
97
|
+
console.log('Created vec0 virtual table');
|
|
98
|
+
// Prepare statements
|
|
99
|
+
const selectStmt = sourceDb.prepare(`
|
|
100
|
+
SELECT embedding, product_name, version, heading_hierarchy, section,
|
|
101
|
+
chunk_id, content, url, hash, chunk_index, total_chunks
|
|
102
|
+
FROM vec_items
|
|
103
|
+
`);
|
|
104
|
+
const insertStmt = targetDb.prepare(`
|
|
105
|
+
INSERT OR REPLACE INTO vec_items
|
|
106
|
+
(embedding, product_name, version, heading_hierarchy, section, chunk_id, content, url, hash, chunk_index, total_chunks)
|
|
107
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
108
|
+
`);
|
|
109
|
+
// Migrate chunks
|
|
110
|
+
let migratedCount = 0;
|
|
111
|
+
let skippedCount = 0;
|
|
112
|
+
const rows = selectStmt.all();
|
|
113
|
+
for (const row of rows) {
|
|
114
|
+
try {
|
|
115
|
+
// The embedding is stored as a blob in the source, convert to Float32Array
|
|
116
|
+
let embedding;
|
|
117
|
+
if (row.embedding && row.embedding.length > 0) {
|
|
118
|
+
// Convert blob to Float32Array
|
|
119
|
+
const buffer = Buffer.from(row.embedding);
|
|
120
|
+
embedding = new Float32Array(buffer.buffer, buffer.byteOffset, buffer.length / 4);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
// Skip rows without embeddings
|
|
124
|
+
console.log(`Skipping chunk ${row.chunk_id.substring(0, 8)}... (no embedding)`);
|
|
125
|
+
skippedCount++;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
insertStmt.run(embedding, row.product_name, row.version, row.heading_hierarchy, row.section, row.chunk_id, row.content, row.url, row.hash, BigInt(row.chunk_index || 0), BigInt(row.total_chunks || 0));
|
|
129
|
+
migratedCount++;
|
|
130
|
+
if (migratedCount % 100 === 0) {
|
|
131
|
+
console.log(`Migrated ${migratedCount} chunks...`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
console.error(`Error migrating chunk ${row.chunk_id}:`, error);
|
|
136
|
+
skippedCount++;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
console.log(`\nMigration complete!`);
|
|
140
|
+
console.log(` Migrated: ${migratedCount} chunks`);
|
|
141
|
+
console.log(` Skipped: ${skippedCount} chunks`);
|
|
142
|
+
console.log(`\nTarget database: ${targetDbPath}`);
|
|
143
|
+
console.log(`You can now use this database with the MCP server.`);
|
|
144
|
+
sourceDb.close();
|
|
145
|
+
targetDb.close();
|
|
146
|
+
}
|
|
147
|
+
// Main
|
|
148
|
+
const args = process.argv.slice(2);
|
|
149
|
+
if (args.length < 2) {
|
|
150
|
+
console.log('Usage: npx ts-node migrate-to-vec0.ts <source-db> <target-db>');
|
|
151
|
+
console.log('');
|
|
152
|
+
console.log('Example:');
|
|
153
|
+
console.log(' npx ts-node migrate-to-vec0.ts ~/Documents/docsync.db ./my-docs.db');
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
migrate(args[0], args[1]);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const config_1 = require("vitest/config");
|
|
4
|
+
exports.default = (0, config_1.defineConfig)({
|
|
5
|
+
test: {
|
|
6
|
+
globals: true,
|
|
7
|
+
environment: 'node',
|
|
8
|
+
include: ['tests/**/*.test.ts'],
|
|
9
|
+
testTimeout: 30000,
|
|
10
|
+
coverage: {
|
|
11
|
+
provider: 'v8',
|
|
12
|
+
include: ['utils.ts', 'logger.ts', 'content-processor.ts', 'database.ts', 'code-chunker.ts', 'doc2vec.ts'],
|
|
13
|
+
exclude: ['mcp/**', 'dist/**', 'tests/**'],
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doc2vec",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"description": "",
|
|
6
6
|
"main": "dist/doc2vec.js",
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tsc && chmod 755 dist/doc2vec.js",
|
|
21
21
|
"start": "node dist/doc2vec.js",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
24
|
+
"test:coverage": "vitest run --coverage",
|
|
22
25
|
"prepublishOnly": "npm run build",
|
|
23
26
|
"prepare": "npm run build"
|
|
24
27
|
},
|
|
@@ -26,6 +29,7 @@
|
|
|
26
29
|
"author": "",
|
|
27
30
|
"license": "ISC",
|
|
28
31
|
"dependencies": {
|
|
32
|
+
"@chonkiejs/core": "^0.0.7",
|
|
29
33
|
"@mozilla/readability": "^0.4.4",
|
|
30
34
|
"@qdrant/js-client-rest": "^1.13.0",
|
|
31
35
|
"@qdrant/qdrant-js": "^1.13.0",
|
|
@@ -36,12 +40,16 @@
|
|
|
36
40
|
"dotenv": "^16.3.1",
|
|
37
41
|
"js-yaml": "^4.1.0",
|
|
38
42
|
"jsdom": "^26.0.0",
|
|
43
|
+
"mammoth": "^1.11.0",
|
|
39
44
|
"openai": "^4.20.1",
|
|
40
45
|
"pdfjs-dist": "^5.3.31",
|
|
41
46
|
"puppeteer": "^24.1.1",
|
|
42
47
|
"sanitize-html": "^2.11.0",
|
|
43
48
|
"sqlite-vec": "0.1.7-alpha.2",
|
|
44
|
-
"
|
|
49
|
+
"tree-sitter-wasms": "^0.1.0",
|
|
50
|
+
"turndown": "^7.1.2",
|
|
51
|
+
"web-tree-sitter": "^0.25.4",
|
|
52
|
+
"word-extractor": "^1.0.4"
|
|
45
53
|
},
|
|
46
54
|
"devDependencies": {
|
|
47
55
|
"@types/better-sqlite3": "^7.6.12",
|
|
@@ -50,7 +58,9 @@
|
|
|
50
58
|
"@types/node": "^20.10.0",
|
|
51
59
|
"@types/sanitize-html": "^2.9.5",
|
|
52
60
|
"@types/turndown": "^5.0.4",
|
|
61
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
53
62
|
"ts-node": "^10.9.1",
|
|
54
|
-
"typescript": "^5.3.2"
|
|
63
|
+
"typescript": "^5.3.2",
|
|
64
|
+
"vitest": "^4.0.18"
|
|
55
65
|
}
|
|
56
66
|
}
|