doc2vec 1.3.0 → 2.0.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.
@@ -0,0 +1,496 @@
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.McpServer = void 0;
40
+ const express_1 = __importDefault(require("express"));
41
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
42
+ const sqliteVec = __importStar(require("sqlite-vec"));
43
+ const openai_1 = __importDefault(require("openai"));
44
+ const crypto_1 = require("crypto");
45
+ class McpServer {
46
+ constructor(port = 3333) {
47
+ this.server = null;
48
+ this.dbPath = null;
49
+ this.openaiApiKey = null;
50
+ this.sessions = new Map();
51
+ this.port = port;
52
+ this.app = (0, express_1.default)();
53
+ this.app.use(express_1.default.json());
54
+ this.setupRoutes();
55
+ }
56
+ setupRoutes() {
57
+ // Health check
58
+ this.app.get('/health', (_req, res) => {
59
+ res.json({ status: 'ok', database: this.dbPath ? 'connected' : 'not connected' });
60
+ });
61
+ // MCP-style tool listing
62
+ this.app.get('/tools', (_req, res) => {
63
+ res.json({
64
+ tools: [{
65
+ name: 'query_documents',
66
+ description: 'Search through synced documents using semantic vector search.',
67
+ inputSchema: {
68
+ type: 'object',
69
+ properties: {
70
+ query: {
71
+ type: 'string',
72
+ description: 'The natural language query to search for.'
73
+ },
74
+ limit: {
75
+ type: 'number',
76
+ description: 'Maximum number of results to return (1-20). Defaults to 5.',
77
+ default: 5
78
+ }
79
+ },
80
+ required: ['query']
81
+ }
82
+ }]
83
+ });
84
+ });
85
+ // Query endpoint
86
+ this.app.post('/query', async (req, res) => {
87
+ try {
88
+ const { query, queryText, limit = 5 } = req.body;
89
+ const searchQuery = query || queryText; // Support both parameter names
90
+ if (!searchQuery) {
91
+ res.status(400).json({ error: 'query is required' });
92
+ return;
93
+ }
94
+ if (!this.dbPath) {
95
+ res.status(503).json({ error: 'Database not configured. Please select a database in the app.' });
96
+ return;
97
+ }
98
+ if (!this.openaiApiKey) {
99
+ res.status(503).json({ error: 'OpenAI API key not configured. Please add your API key in the app.' });
100
+ return;
101
+ }
102
+ const results = await this.queryDatabase(searchQuery, Math.min(limit, 20));
103
+ res.json({
104
+ query: searchQuery,
105
+ count: results.length,
106
+ results
107
+ });
108
+ }
109
+ catch (error) {
110
+ console.error('Query error:', error);
111
+ res.status(500).json({ error: error.message });
112
+ }
113
+ });
114
+ // MCP Streamable HTTP endpoint - proper JSON-RPC handling
115
+ this.app.post('/mcp', async (req, res) => {
116
+ try {
117
+ const request = req.body;
118
+ const { jsonrpc, id, method, params } = request;
119
+ // Get or create session
120
+ let sessionId = req.headers['mcp-session-id'];
121
+ if (!sessionId && method === 'initialize') {
122
+ sessionId = (0, crypto_1.randomUUID)();
123
+ this.sessions.set(sessionId, true);
124
+ }
125
+ // Set session header
126
+ if (sessionId) {
127
+ res.setHeader('mcp-session-id', sessionId);
128
+ }
129
+ console.log(`MCP Request: ${method}`, params ? JSON.stringify(params).substring(0, 100) : '');
130
+ // Handle MCP methods
131
+ switch (method) {
132
+ case 'initialize':
133
+ res.json({
134
+ jsonrpc: '2.0',
135
+ id,
136
+ result: {
137
+ protocolVersion: '2024-11-05',
138
+ capabilities: {
139
+ tools: {}
140
+ },
141
+ serverInfo: {
142
+ name: 'docs4ai',
143
+ version: '1.0.0'
144
+ }
145
+ }
146
+ });
147
+ return;
148
+ case 'notifications/initialized':
149
+ // Client acknowledged initialization - no response needed for notifications
150
+ res.status(204).send();
151
+ return;
152
+ case 'tools/list':
153
+ res.json({
154
+ jsonrpc: '2.0',
155
+ id,
156
+ result: {
157
+ tools: [
158
+ {
159
+ name: 'query_documents',
160
+ description: 'Search through synced documents using semantic vector search. Returns relevant document chunks with their chunk_index and total_chunks so you can retrieve additional chunks from the same document using get_chunks.',
161
+ inputSchema: {
162
+ type: 'object',
163
+ properties: {
164
+ query: {
165
+ type: 'string',
166
+ description: 'The natural language search query'
167
+ },
168
+ limit: {
169
+ type: 'number',
170
+ description: 'Maximum number of results to return (1-20)',
171
+ default: 5
172
+ }
173
+ },
174
+ required: ['query']
175
+ }
176
+ },
177
+ {
178
+ name: 'get_chunks',
179
+ description: 'Retrieve specific chunks from a document by file path. Use this to get more context from a document after finding it with query_documents. You can retrieve individual chunks or a range of chunks.',
180
+ inputSchema: {
181
+ type: 'object',
182
+ properties: {
183
+ file_path: {
184
+ type: 'string',
185
+ description: 'The file path (url) of the document to retrieve chunks from'
186
+ },
187
+ chunk_indices: {
188
+ type: 'array',
189
+ items: { type: 'number' },
190
+ description: 'Array of chunk indices to retrieve (0-based). If not provided, returns all chunks.'
191
+ }
192
+ },
193
+ required: ['file_path']
194
+ }
195
+ }
196
+ ]
197
+ }
198
+ });
199
+ return;
200
+ case 'tools/call':
201
+ const { name, arguments: args } = params || {};
202
+ if (name === 'query_documents') {
203
+ if (!this.dbPath || !this.openaiApiKey) {
204
+ res.json({
205
+ jsonrpc: '2.0',
206
+ id,
207
+ result: {
208
+ content: [{ type: 'text', text: 'Error: Database or API key not configured in Docs4ai app. Please configure them in the app settings.' }],
209
+ isError: true
210
+ }
211
+ });
212
+ return;
213
+ }
214
+ const query = args?.query || args?.queryText || '';
215
+ if (!query) {
216
+ res.json({
217
+ jsonrpc: '2.0',
218
+ id,
219
+ result: {
220
+ content: [{ type: 'text', text: 'Error: query parameter is required' }],
221
+ isError: true
222
+ }
223
+ });
224
+ return;
225
+ }
226
+ try {
227
+ const results = await this.queryDatabase(query, Math.min(args?.limit || 5, 20));
228
+ if (results.length === 0) {
229
+ res.json({
230
+ jsonrpc: '2.0',
231
+ id,
232
+ result: {
233
+ content: [{ type: 'text', text: `No results found for "${query}"` }]
234
+ }
235
+ });
236
+ return;
237
+ }
238
+ const formatted = results.map((r, i) => `**Result ${i + 1}** (distance: ${r.distance.toFixed(4)})\n` +
239
+ `File: ${r.url}\n` +
240
+ `Section: ${r.section}\n` +
241
+ `Chunk: ${r.chunk_index + 1} of ${r.total_chunks}\n` +
242
+ `${r.content}\n` +
243
+ `---`).join('\n\n');
244
+ res.json({
245
+ jsonrpc: '2.0',
246
+ id,
247
+ result: {
248
+ content: [{ type: 'text', text: `Found ${results.length} results for "${query}":\n\n${formatted}` }]
249
+ }
250
+ });
251
+ return;
252
+ }
253
+ catch (queryError) {
254
+ res.json({
255
+ jsonrpc: '2.0',
256
+ id,
257
+ result: {
258
+ content: [{ type: 'text', text: `Error querying database: ${queryError.message}` }],
259
+ isError: true
260
+ }
261
+ });
262
+ return;
263
+ }
264
+ }
265
+ if (name === 'get_chunks') {
266
+ if (!this.dbPath) {
267
+ res.json({
268
+ jsonrpc: '2.0',
269
+ id,
270
+ result: {
271
+ content: [{ type: 'text', text: 'Error: Database not configured in Docs4ai app.' }],
272
+ isError: true
273
+ }
274
+ });
275
+ return;
276
+ }
277
+ const filePath = args?.file_path || '';
278
+ if (!filePath) {
279
+ res.json({
280
+ jsonrpc: '2.0',
281
+ id,
282
+ result: {
283
+ content: [{ type: 'text', text: 'Error: file_path parameter is required' }],
284
+ isError: true
285
+ }
286
+ });
287
+ return;
288
+ }
289
+ try {
290
+ const chunks = this.getChunksForFile(filePath, args?.chunk_indices);
291
+ if (chunks.length === 0) {
292
+ res.json({
293
+ jsonrpc: '2.0',
294
+ id,
295
+ result: {
296
+ content: [{ type: 'text', text: `No chunks found for file: ${filePath}` }]
297
+ }
298
+ });
299
+ return;
300
+ }
301
+ const formatted = chunks.map((c) => `**Chunk ${c.chunk_index + 1} of ${c.total_chunks}**\n` +
302
+ `Section: ${c.section}\n` +
303
+ `${c.content}\n` +
304
+ `---`).join('\n\n');
305
+ res.json({
306
+ jsonrpc: '2.0',
307
+ id,
308
+ result: {
309
+ content: [{ type: 'text', text: `Retrieved ${chunks.length} chunk(s) from "${filePath}":\n\n${formatted}` }]
310
+ }
311
+ });
312
+ return;
313
+ }
314
+ catch (chunkError) {
315
+ res.json({
316
+ jsonrpc: '2.0',
317
+ id,
318
+ result: {
319
+ content: [{ type: 'text', text: `Error retrieving chunks: ${chunkError.message}` }],
320
+ isError: true
321
+ }
322
+ });
323
+ return;
324
+ }
325
+ }
326
+ res.json({
327
+ jsonrpc: '2.0',
328
+ id,
329
+ error: {
330
+ code: -32601,
331
+ message: `Unknown tool: ${name}`
332
+ }
333
+ });
334
+ return;
335
+ default:
336
+ res.json({
337
+ jsonrpc: '2.0',
338
+ id,
339
+ error: {
340
+ code: -32601,
341
+ message: `Method not found: ${method}`
342
+ }
343
+ });
344
+ return;
345
+ }
346
+ }
347
+ catch (error) {
348
+ console.error('MCP error:', error);
349
+ res.json({
350
+ jsonrpc: '2.0',
351
+ id: req.body?.id,
352
+ error: {
353
+ code: -32603,
354
+ message: error.message
355
+ }
356
+ });
357
+ }
358
+ });
359
+ // Handle GET for SSE (not implemented, return 404)
360
+ this.app.get('/mcp', (_req, res) => {
361
+ res.status(404).json({ error: 'SSE not supported, use POST for Streamable HTTP' });
362
+ });
363
+ }
364
+ async queryDatabase(queryText, limit) {
365
+ if (!this.dbPath || !this.openaiApiKey) {
366
+ throw new Error('Database or API key not configured');
367
+ }
368
+ // Generate embedding for query
369
+ const openai = new openai_1.default({ apiKey: this.openaiApiKey });
370
+ const embeddingResponse = await openai.embeddings.create({
371
+ model: 'text-embedding-3-large',
372
+ input: queryText
373
+ });
374
+ const queryEmbedding = embeddingResponse.data[0].embedding;
375
+ // Query database
376
+ const db = new better_sqlite3_1.default(this.dbPath);
377
+ sqliteVec.load(db);
378
+ try {
379
+ const stmt = db.prepare(`
380
+ SELECT
381
+ chunk_id,
382
+ distance,
383
+ content,
384
+ url,
385
+ section,
386
+ heading_hierarchy,
387
+ chunk_index,
388
+ total_chunks
389
+ FROM vec_items
390
+ WHERE embedding MATCH ?
391
+ ORDER BY distance
392
+ LIMIT ?
393
+ `);
394
+ const rows = stmt.all(new Float32Array(queryEmbedding), limit);
395
+ return rows;
396
+ }
397
+ finally {
398
+ db.close();
399
+ }
400
+ }
401
+ getChunksForFile(filePath, chunkIndices) {
402
+ if (!this.dbPath) {
403
+ throw new Error('Database not configured');
404
+ }
405
+ const db = new better_sqlite3_1.default(this.dbPath);
406
+ sqliteVec.load(db);
407
+ try {
408
+ let rows;
409
+ if (chunkIndices && chunkIndices.length > 0) {
410
+ // Get specific chunks by index
411
+ const placeholders = chunkIndices.map(() => '?').join(',');
412
+ const stmt = db.prepare(`
413
+ SELECT
414
+ chunk_id,
415
+ content,
416
+ section,
417
+ heading_hierarchy,
418
+ chunk_index,
419
+ total_chunks
420
+ FROM vec_items
421
+ WHERE url = ? AND chunk_index IN (${placeholders})
422
+ ORDER BY chunk_index
423
+ `);
424
+ rows = stmt.all(filePath, ...chunkIndices);
425
+ }
426
+ else {
427
+ // Get all chunks for the file
428
+ const stmt = db.prepare(`
429
+ SELECT
430
+ chunk_id,
431
+ content,
432
+ section,
433
+ heading_hierarchy,
434
+ chunk_index,
435
+ total_chunks
436
+ FROM vec_items
437
+ WHERE url = ?
438
+ ORDER BY chunk_index
439
+ `);
440
+ rows = stmt.all(filePath);
441
+ }
442
+ return rows;
443
+ }
444
+ finally {
445
+ db.close();
446
+ }
447
+ }
448
+ setDatabase(dbPath) {
449
+ this.dbPath = dbPath;
450
+ console.log(`MCP Server: Database set to ${dbPath}`);
451
+ }
452
+ setApiKey(apiKey) {
453
+ this.openaiApiKey = apiKey;
454
+ }
455
+ start() {
456
+ return new Promise((resolve, reject) => {
457
+ try {
458
+ const server = this.app.listen(this.port, () => {
459
+ console.log(`MCP Server running on http://localhost:${this.port}`);
460
+ resolve();
461
+ });
462
+ server.on('error', (error) => {
463
+ if (error.code === 'EADDRINUSE') {
464
+ console.error(`MCP Server: Port ${this.port} already in use`);
465
+ }
466
+ reject(error);
467
+ });
468
+ this.server = server;
469
+ }
470
+ catch (error) {
471
+ reject(error);
472
+ }
473
+ });
474
+ }
475
+ stop() {
476
+ return new Promise((resolve) => {
477
+ if (this.server) {
478
+ this.server.close(() => {
479
+ console.log('MCP Server stopped');
480
+ this.server = null;
481
+ resolve();
482
+ });
483
+ }
484
+ else {
485
+ resolve();
486
+ }
487
+ });
488
+ }
489
+ isRunning() {
490
+ return this.server !== null;
491
+ }
492
+ getPort() {
493
+ return this.port;
494
+ }
495
+ }
496
+ exports.McpServer = McpServer;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const electron_1 = require("electron");
4
+ electron_1.contextBridge.exposeInMainWorld('api', {
5
+ getSettings: () => electron_1.ipcRenderer.invoke('get-settings'),
6
+ saveSettings: (settings) => electron_1.ipcRenderer.invoke('save-settings', settings),
7
+ selectFolder: () => electron_1.ipcRenderer.invoke('select-folder'),
8
+ selectDatabase: () => electron_1.ipcRenderer.invoke('select-database'),
9
+ getStats: () => electron_1.ipcRenderer.invoke('get-stats'),
10
+ getFiles: () => electron_1.ipcRenderer.invoke('get-files'),
11
+ getChunks: (filePath) => electron_1.ipcRenderer.invoke('get-chunks', filePath),
12
+ startWatching: () => electron_1.ipcRenderer.invoke('start-watching'),
13
+ stopWatching: () => electron_1.ipcRenderer.invoke('stop-watching'),
14
+ forceSync: () => electron_1.ipcRenderer.invoke('force-sync'),
15
+ startMcpServer: () => electron_1.ipcRenderer.invoke('start-mcp-server'),
16
+ stopMcpServer: () => electron_1.ipcRenderer.invoke('stop-mcp-server'),
17
+ getMcpStatus: () => electron_1.ipcRenderer.invoke('get-mcp-status'),
18
+ onStatsUpdate: (callback) => {
19
+ electron_1.ipcRenderer.on('stats-update', (_event, stats) => callback(stats));
20
+ },
21
+ onApiKeyError: (callback) => {
22
+ electron_1.ipcRenderer.on('api-key-error', (_event, message) => callback(message));
23
+ }
24
+ });