directory-indexer 0.3.0 → 1.1.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 +4 -1
- package/dist/cli-handlers.js +5 -2
- package/dist/cli-handlers.js.map +1 -1
- package/dist/cli.js +9 -2
- package/dist/cli.js.map +1 -1
- package/dist/config.js +13 -5
- package/dist/config.js.map +1 -1
- package/dist/embedding.js +2 -2
- package/dist/embedding.js.map +1 -1
- package/dist/indexing.js +53 -27
- package/dist/indexing.js.map +1 -1
- package/dist/mcp-handlers.js +91 -101
- package/dist/mcp-handlers.js.map +1 -1
- package/dist/mcp.js +3 -3
- package/dist/mcp.js.map +1 -1
- package/dist/path-validation.js +2 -1
- package/dist/path-validation.js.map +1 -1
- package/dist/prerequisites.js +5 -8
- package/dist/prerequisites.js.map +1 -1
- package/dist/reset.js +21 -31
- package/dist/reset.js.map +1 -1
- package/dist/search.js +14 -3
- package/dist/search.js.map +1 -1
- package/dist/storage.js +151 -70
- package/dist/storage.js.map +1 -1
- package/dist/utils.js +10 -1
- package/dist/utils.js.map +1 -1
- package/package.json +16 -22
package/dist/storage.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Database from "better-sqlite3";
|
|
2
|
-
import {
|
|
2
|
+
import { mkdirSync } from "node:fs";
|
|
3
3
|
import { dirname } from "path";
|
|
4
4
|
function escapeLike(value) {
|
|
5
5
|
return value.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
@@ -8,10 +8,11 @@ function directoryLikeClause(column, dirPath) {
|
|
|
8
8
|
const escaped = escapeLike(dirPath);
|
|
9
9
|
return {
|
|
10
10
|
clause: `(${column} = ? OR ${column} LIKE ? ESCAPE '\\' OR ${column} LIKE ? ESCAPE '\\')`,
|
|
11
|
-
params: [dirPath, `${escaped}/%`, `${escaped}
|
|
11
|
+
params: [dirPath, `${escaped}/%`, `${escaped}\\\\%`]
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
const openStorageInstances = /* @__PURE__ */ new Set();
|
|
15
|
+
const sharedStorage = /* @__PURE__ */ new Map();
|
|
15
16
|
function closeAllStorage() {
|
|
16
17
|
for (const instance of openStorageInstances) {
|
|
17
18
|
try {
|
|
@@ -19,6 +20,7 @@ function closeAllStorage() {
|
|
|
19
20
|
} catch {
|
|
20
21
|
}
|
|
21
22
|
}
|
|
23
|
+
sharedStorage.clear();
|
|
22
24
|
}
|
|
23
25
|
class StorageError extends Error {
|
|
24
26
|
constructor(message, cause) {
|
|
@@ -31,27 +33,33 @@ class QdrantClient {
|
|
|
31
33
|
constructor(config) {
|
|
32
34
|
this.config = config;
|
|
33
35
|
}
|
|
36
|
+
qdrantFetch(path, init) {
|
|
37
|
+
const headers = { "Content-Type": "application/json" };
|
|
38
|
+
if (this.config.storage.qdrantApiKey) {
|
|
39
|
+
headers["api-key"] = this.config.storage.qdrantApiKey;
|
|
40
|
+
}
|
|
41
|
+
return fetch(`${this.config.storage.qdrantEndpoint}${path}`, { ...init, headers });
|
|
42
|
+
}
|
|
34
43
|
async healthCheck() {
|
|
35
44
|
try {
|
|
36
|
-
const response = await
|
|
45
|
+
const response = await this.qdrantFetch("/healthz");
|
|
37
46
|
return response.ok;
|
|
38
47
|
} catch {
|
|
39
48
|
return false;
|
|
40
49
|
}
|
|
41
50
|
}
|
|
42
|
-
async createCollection() {
|
|
51
|
+
async createCollection(dimension) {
|
|
43
52
|
const collectionName = this.config.storage.qdrantCollection;
|
|
44
53
|
try {
|
|
45
|
-
const checkResponse = await
|
|
54
|
+
const checkResponse = await this.qdrantFetch(`/collections/${collectionName}`);
|
|
46
55
|
if (checkResponse.ok) {
|
|
47
56
|
return;
|
|
48
57
|
}
|
|
49
|
-
const createResponse = await
|
|
58
|
+
const createResponse = await this.qdrantFetch(`/collections/${collectionName}`, {
|
|
50
59
|
method: "PUT",
|
|
51
|
-
headers: { "Content-Type": "application/json" },
|
|
52
60
|
body: JSON.stringify({
|
|
53
61
|
vectors: {
|
|
54
|
-
size:
|
|
62
|
+
size: dimension,
|
|
55
63
|
distance: "Cosine"
|
|
56
64
|
}
|
|
57
65
|
})
|
|
@@ -66,9 +74,8 @@ class QdrantClient {
|
|
|
66
74
|
async upsertPoints(points) {
|
|
67
75
|
const collectionName = this.config.storage.qdrantCollection;
|
|
68
76
|
try {
|
|
69
|
-
const response = await
|
|
77
|
+
const response = await this.qdrantFetch(`/collections/${collectionName}/points`, {
|
|
70
78
|
method: "PUT",
|
|
71
|
-
headers: { "Content-Type": "application/json" },
|
|
72
79
|
body: JSON.stringify({ points })
|
|
73
80
|
});
|
|
74
81
|
if (!response.ok) {
|
|
@@ -90,11 +97,13 @@ class QdrantClient {
|
|
|
90
97
|
if (filter) {
|
|
91
98
|
searchBody.filter = filter;
|
|
92
99
|
}
|
|
93
|
-
const response = await
|
|
100
|
+
const response = await this.qdrantFetch(`/collections/${collectionName}/points/search`, {
|
|
94
101
|
method: "POST",
|
|
95
|
-
headers: { "Content-Type": "application/json" },
|
|
96
102
|
body: JSON.stringify(searchBody)
|
|
97
103
|
});
|
|
104
|
+
if (response.status === 404) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
98
107
|
if (!response.ok) {
|
|
99
108
|
const errorBody = await response.text();
|
|
100
109
|
throw new Error(`Failed to search points: ${response.statusText} - ${errorBody}`);
|
|
@@ -113,11 +122,13 @@ class QdrantClient {
|
|
|
113
122
|
async deletePoints(ids) {
|
|
114
123
|
const collectionName = this.config.storage.qdrantCollection;
|
|
115
124
|
try {
|
|
116
|
-
const response = await
|
|
125
|
+
const response = await this.qdrantFetch(`/collections/${collectionName}/points/delete`, {
|
|
117
126
|
method: "POST",
|
|
118
|
-
headers: { "Content-Type": "application/json" },
|
|
119
127
|
body: JSON.stringify({ points: ids })
|
|
120
128
|
});
|
|
129
|
+
if (response.status === 404) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
121
132
|
if (!response.ok) {
|
|
122
133
|
throw new Error(`Failed to delete points: ${response.statusText}`);
|
|
123
134
|
}
|
|
@@ -128,9 +139,8 @@ class QdrantClient {
|
|
|
128
139
|
async deletePointsByFilePath(filePath) {
|
|
129
140
|
const collectionName = this.config.storage.qdrantCollection;
|
|
130
141
|
try {
|
|
131
|
-
const response = await
|
|
142
|
+
const response = await this.qdrantFetch(`/collections/${collectionName}/points/delete`, {
|
|
132
143
|
method: "POST",
|
|
133
|
-
headers: { "Content-Type": "application/json" },
|
|
134
144
|
body: JSON.stringify({
|
|
135
145
|
filter: {
|
|
136
146
|
must: [
|
|
@@ -142,6 +152,9 @@ class QdrantClient {
|
|
|
142
152
|
}
|
|
143
153
|
})
|
|
144
154
|
});
|
|
155
|
+
if (response.status === 404) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
145
158
|
if (!response.ok) {
|
|
146
159
|
const errorText = await response.text();
|
|
147
160
|
throw new Error(`Failed to delete points by file path: ${response.status} ${response.statusText} - ${errorText}`);
|
|
@@ -157,11 +170,13 @@ class QdrantClient {
|
|
|
157
170
|
if (filter) {
|
|
158
171
|
countBody.filter = filter;
|
|
159
172
|
}
|
|
160
|
-
const response = await
|
|
173
|
+
const response = await this.qdrantFetch(`/collections/${collectionName}/points/count`, {
|
|
161
174
|
method: "POST",
|
|
162
|
-
headers: { "Content-Type": "application/json" },
|
|
163
175
|
body: JSON.stringify(countBody)
|
|
164
176
|
});
|
|
177
|
+
if (response.status === 404) {
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
165
180
|
if (!response.ok) {
|
|
166
181
|
throw new Error(`Failed to count points: ${response.statusText}`);
|
|
167
182
|
}
|
|
@@ -174,29 +189,40 @@ class QdrantClient {
|
|
|
174
189
|
async scrollPoints(filter, limit = 1e3) {
|
|
175
190
|
const collectionName = this.config.storage.qdrantCollection;
|
|
176
191
|
try {
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
192
|
+
const points = [];
|
|
193
|
+
let offset = null;
|
|
194
|
+
do {
|
|
195
|
+
const scrollBody = {
|
|
196
|
+
limit,
|
|
197
|
+
with_payload: true,
|
|
198
|
+
with_vector: false
|
|
199
|
+
};
|
|
200
|
+
if (filter) {
|
|
201
|
+
scrollBody.filter = filter;
|
|
202
|
+
}
|
|
203
|
+
if (offset !== null) {
|
|
204
|
+
scrollBody.offset = offset;
|
|
205
|
+
}
|
|
206
|
+
const response = await this.qdrantFetch(`/collections/${collectionName}/points/scroll`, {
|
|
207
|
+
method: "POST",
|
|
208
|
+
body: JSON.stringify(scrollBody)
|
|
209
|
+
});
|
|
210
|
+
if (response.status === 404) {
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
if (!response.ok) {
|
|
214
|
+
throw new Error(`Failed to scroll points: ${response.statusText}`);
|
|
215
|
+
}
|
|
216
|
+
const data = await response.json();
|
|
217
|
+
points.push(...data.result.points.map((item) => ({
|
|
218
|
+
id: item.id,
|
|
219
|
+
vector: [],
|
|
220
|
+
payload: item.payload,
|
|
221
|
+
score: 0
|
|
222
|
+
})));
|
|
223
|
+
offset = data.result.next_page_offset ?? null;
|
|
224
|
+
} while (offset !== null);
|
|
225
|
+
return points;
|
|
200
226
|
} catch (error) {
|
|
201
227
|
throw new StorageError(`Failed to scroll points in Qdrant`, error);
|
|
202
228
|
}
|
|
@@ -204,7 +230,7 @@ class QdrantClient {
|
|
|
204
230
|
async getCollectionInfo() {
|
|
205
231
|
const collectionName = this.config.storage.qdrantCollection;
|
|
206
232
|
try {
|
|
207
|
-
const response = await
|
|
233
|
+
const response = await this.qdrantFetch(`/collections/${collectionName}`);
|
|
208
234
|
if (!response.ok) {
|
|
209
235
|
if (response.status === 404) {
|
|
210
236
|
return null;
|
|
@@ -212,9 +238,14 @@ class QdrantClient {
|
|
|
212
238
|
throw new Error(`Failed to get collection info: ${response.statusText}`);
|
|
213
239
|
}
|
|
214
240
|
const data = await response.json();
|
|
215
|
-
|
|
241
|
+
const vectors = data.result?.config?.params?.vectors;
|
|
242
|
+
const info = {
|
|
216
243
|
vectors_count: data.result?.points_count || data.result?.vectors_count || 0
|
|
217
244
|
};
|
|
245
|
+
if (vectors && typeof vectors === "object" && typeof vectors.size === "number") {
|
|
246
|
+
info.vectorSize = vectors.size;
|
|
247
|
+
}
|
|
248
|
+
return info;
|
|
218
249
|
} catch (error) {
|
|
219
250
|
throw new StorageError(`Failed to get collection info from Qdrant`, error);
|
|
220
251
|
}
|
|
@@ -222,11 +253,8 @@ class QdrantClient {
|
|
|
222
253
|
async deleteCollection() {
|
|
223
254
|
const collectionName = this.config.storage.qdrantCollection;
|
|
224
255
|
try {
|
|
225
|
-
const response = await
|
|
226
|
-
method: "DELETE"
|
|
227
|
-
headers: this.config.storage.qdrantApiKey ? {
|
|
228
|
-
"api-key": this.config.storage.qdrantApiKey
|
|
229
|
-
} : {}
|
|
256
|
+
const response = await this.qdrantFetch(`/collections/${collectionName}`, {
|
|
257
|
+
method: "DELETE"
|
|
230
258
|
});
|
|
231
259
|
if (!response.ok && response.status !== 404) {
|
|
232
260
|
throw new Error(`Failed to delete collection: ${response.statusText}`);
|
|
@@ -245,7 +273,7 @@ class SQLiteStorage {
|
|
|
245
273
|
db;
|
|
246
274
|
initializeDatabase() {
|
|
247
275
|
try {
|
|
248
|
-
|
|
276
|
+
mkdirSync(dirname(this.config.storage.sqlitePath), { recursive: true });
|
|
249
277
|
const db = new Database(this.config.storage.sqlitePath);
|
|
250
278
|
db.exec(`
|
|
251
279
|
CREATE TABLE IF NOT EXISTS directories (
|
|
@@ -269,6 +297,11 @@ class SQLiteStorage {
|
|
|
269
297
|
CREATE INDEX IF NOT EXISTS idx_files_path ON files(path);
|
|
270
298
|
CREATE INDEX IF NOT EXISTS idx_files_hash ON files(hash);
|
|
271
299
|
CREATE INDEX IF NOT EXISTS idx_directories_path ON directories(path);
|
|
300
|
+
|
|
301
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
302
|
+
key TEXT PRIMARY KEY,
|
|
303
|
+
value TEXT
|
|
304
|
+
);
|
|
272
305
|
`);
|
|
273
306
|
db.pragma("journal_mode = WAL");
|
|
274
307
|
db.pragma("busy_timeout = 5000");
|
|
@@ -292,6 +325,21 @@ class SQLiteStorage {
|
|
|
292
325
|
throw new StorageError(`Failed to get directory record`, error);
|
|
293
326
|
}
|
|
294
327
|
}
|
|
328
|
+
async getDirectoryByCaseInsensitive(path) {
|
|
329
|
+
try {
|
|
330
|
+
const stmt = this.db.prepare("SELECT * FROM directories WHERE path = ? COLLATE NOCASE");
|
|
331
|
+
const row = stmt.get(path);
|
|
332
|
+
if (!row) return null;
|
|
333
|
+
return {
|
|
334
|
+
id: row.id,
|
|
335
|
+
path: row.path,
|
|
336
|
+
status: row.status,
|
|
337
|
+
indexedAt: new Date(row.indexed_at)
|
|
338
|
+
};
|
|
339
|
+
} catch (error) {
|
|
340
|
+
throw new StorageError(`Failed to get directory record`, error);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
295
343
|
async upsertDirectory(path, status) {
|
|
296
344
|
try {
|
|
297
345
|
const stmt = this.db.prepare(`
|
|
@@ -312,7 +360,7 @@ class SQLiteStorage {
|
|
|
312
360
|
id: row.id,
|
|
313
361
|
path: row.path,
|
|
314
362
|
size: row.size,
|
|
315
|
-
modifiedTime: new Date(row.modified_time
|
|
363
|
+
modifiedTime: new Date(row.modified_time),
|
|
316
364
|
hash: row.hash,
|
|
317
365
|
parentDirs: JSON.parse(row.parent_dirs),
|
|
318
366
|
chunks: row.chunks_json ? JSON.parse(row.chunks_json) : [],
|
|
@@ -362,7 +410,7 @@ class SQLiteStorage {
|
|
|
362
410
|
id: row.id,
|
|
363
411
|
path: row.path,
|
|
364
412
|
size: row.size,
|
|
365
|
-
modifiedTime: new Date(row.modified_time
|
|
413
|
+
modifiedTime: new Date(row.modified_time),
|
|
366
414
|
hash: row.hash,
|
|
367
415
|
parentDirs: JSON.parse(row.parent_dirs),
|
|
368
416
|
chunks: row.chunks_json ? JSON.parse(row.chunks_json) : [],
|
|
@@ -380,16 +428,52 @@ class SQLiteStorage {
|
|
|
380
428
|
const result = this.db.prepare(`DELETE FROM files WHERE ${clause}`).run(...params);
|
|
381
429
|
return result.changes;
|
|
382
430
|
}
|
|
431
|
+
getMeta(key) {
|
|
432
|
+
const row = this.db.prepare("SELECT value FROM meta WHERE key = ?").get(key);
|
|
433
|
+
return row?.value ?? null;
|
|
434
|
+
}
|
|
435
|
+
setMeta(key, value) {
|
|
436
|
+
this.db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)").run(key, value);
|
|
437
|
+
}
|
|
383
438
|
close() {
|
|
384
439
|
openStorageInstances.delete(this);
|
|
385
440
|
this.db.close();
|
|
386
441
|
}
|
|
387
442
|
}
|
|
388
443
|
async function initializeStorage(config) {
|
|
444
|
+
const dbPath = config.storage.sqlitePath;
|
|
445
|
+
const cached = sharedStorage.get(dbPath);
|
|
446
|
+
if (cached) {
|
|
447
|
+
return cached;
|
|
448
|
+
}
|
|
389
449
|
const sqlite = new SQLiteStorage(config);
|
|
390
450
|
const qdrant = new QdrantClient(config);
|
|
391
|
-
|
|
392
|
-
|
|
451
|
+
const storage = { sqlite, qdrant };
|
|
452
|
+
sharedStorage.set(dbPath, storage);
|
|
453
|
+
return storage;
|
|
454
|
+
}
|
|
455
|
+
function closeSharedStorageForPath(dbPath) {
|
|
456
|
+
const shared = sharedStorage.get(dbPath);
|
|
457
|
+
if (!shared) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
sharedStorage.delete(dbPath);
|
|
461
|
+
shared.sqlite.close();
|
|
462
|
+
}
|
|
463
|
+
async function ensureCollectionDimensions(sqlite, qdrant, config, dimension) {
|
|
464
|
+
const info = await qdrant.getCollectionInfo();
|
|
465
|
+
if (!info) {
|
|
466
|
+
await qdrant.createCollection(dimension);
|
|
467
|
+
} else if (info.vectorSize !== void 0 && info.vectorSize !== dimension) {
|
|
468
|
+
const provider = sqlite.getMeta("embedding_provider");
|
|
469
|
+
const recordedDims = sqlite.getMeta("embedding_dims");
|
|
470
|
+
const recorded = provider && recordedDims ? ` (meta table records provider '${provider}' with ${recordedDims} dimensions)` : "";
|
|
471
|
+
throw new StorageError(
|
|
472
|
+
`Qdrant collection '${config.storage.qdrantCollection}' was created with ${info.vectorSize}-dimensional vectors but the active embedding provider produces ${dimension}-dimensional vectors${recorded}. Run \`directory-indexer reset\` to delete the existing collection, then re-index.`
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
sqlite.setMeta("embedding_provider", config.embedding.provider);
|
|
476
|
+
sqlite.setMeta("embedding_dims", String(dimension));
|
|
393
477
|
}
|
|
394
478
|
async function initDatabase(dbPath) {
|
|
395
479
|
return new Database(dbPath);
|
|
@@ -425,6 +509,7 @@ async function getResetPreview(config) {
|
|
|
425
509
|
}
|
|
426
510
|
async function clearDatabase(config) {
|
|
427
511
|
try {
|
|
512
|
+
closeSharedStorageForPath(config.storage.sqlitePath);
|
|
428
513
|
if (!await import("fs").then((fs) => fs.existsSync(config.storage.sqlitePath))) {
|
|
429
514
|
return true;
|
|
430
515
|
}
|
|
@@ -589,13 +674,12 @@ async function checkQdrantConsistency(sqlite, config) {
|
|
|
589
674
|
}
|
|
590
675
|
const collectionName = config.storage.qdrantCollection;
|
|
591
676
|
try {
|
|
592
|
-
const
|
|
593
|
-
if (!
|
|
677
|
+
const collectionInfo = await qdrant.getCollectionInfo();
|
|
678
|
+
if (!collectionInfo) {
|
|
594
679
|
issues.push(`Vector collection '${collectionName}' not found (normal during first-time setup)`);
|
|
595
680
|
return { isConsistent: false, issues };
|
|
596
681
|
}
|
|
597
|
-
const
|
|
598
|
-
const qdrantPointCount = collectionInfo.result?.points_count || 0;
|
|
682
|
+
const qdrantPointCount = collectionInfo.vectors_count || 0;
|
|
599
683
|
const sqliteChunkCount = totalChunks.count || 0;
|
|
600
684
|
if (Math.abs(qdrantPointCount - sqliteChunkCount) > 0) {
|
|
601
685
|
if (qdrantPointCount > sqliteChunkCount) {
|
|
@@ -625,7 +709,7 @@ async function getIndexStatus() {
|
|
|
625
709
|
const filesCount = filesStmt.get();
|
|
626
710
|
const chunksStmt = sqlite.db.prepare("SELECT SUM(json_array_length(chunks_json)) as count FROM files WHERE chunks_json IS NOT NULL");
|
|
627
711
|
const chunksCount = chunksStmt.get();
|
|
628
|
-
const lastIndexedStmt = sqlite.db.prepare("SELECT MAX(indexed_at) as last_indexed FROM directories WHERE indexed_at > 0");
|
|
712
|
+
const lastIndexedStmt = sqlite.db.prepare("SELECT MAX(indexed_at) as last_indexed FROM directories WHERE indexed_at > 0 AND status = 'completed'");
|
|
629
713
|
const lastIndexedResult = lastIndexedStmt.get();
|
|
630
714
|
const errorsStmt = sqlite.db.prepare("SELECT errors_json FROM files WHERE errors_json IS NOT NULL");
|
|
631
715
|
const errorRows = errorsStmt.all();
|
|
@@ -638,19 +722,14 @@ async function getIndexStatus() {
|
|
|
638
722
|
allErrors.push("Failed to parse error JSON");
|
|
639
723
|
}
|
|
640
724
|
});
|
|
641
|
-
const directoriesDetailStmt = sqlite.db.prepare(
|
|
642
|
-
SELECT
|
|
643
|
-
d.path,
|
|
644
|
-
d.status,
|
|
645
|
-
d.indexed_at,
|
|
646
|
-
(SELECT COUNT(*) FROM files f WHERE f.path = d.path OR f.path LIKE REPLACE(d.path, '\\', '\\\\') || '/%' ESCAPE '\\' OR f.path LIKE REPLACE(d.path, '\\', '\\\\') || '\\%' ESCAPE '\\') as files_count,
|
|
647
|
-
(SELECT COALESCE(SUM(json_array_length(f.chunks_json)), 0) FROM files f WHERE (f.path = d.path OR f.path LIKE REPLACE(d.path, '\\', '\\\\') || '/%' ESCAPE '\\' OR f.path LIKE REPLACE(d.path, '\\', '\\\\') || '\\%' ESCAPE '\\') AND f.chunks_json IS NOT NULL) as chunks_count
|
|
648
|
-
FROM directories d
|
|
649
|
-
ORDER BY d.indexed_at DESC
|
|
650
|
-
`);
|
|
725
|
+
const directoriesDetailStmt = sqlite.db.prepare("SELECT path, status, indexed_at FROM directories ORDER BY indexed_at DESC");
|
|
651
726
|
const directoryDetails = directoriesDetailStmt.all();
|
|
652
727
|
const directories = directoryDetails.map((row) => {
|
|
653
728
|
const { clause, params } = directoryLikeClause("path", row.path);
|
|
729
|
+
const filesByDirStmt = sqlite.db.prepare(`SELECT COUNT(*) as count FROM files WHERE ${clause}`);
|
|
730
|
+
const filesCount2 = filesByDirStmt.get(...params);
|
|
731
|
+
const chunksByDirStmt = sqlite.db.prepare(`SELECT COALESCE(SUM(json_array_length(chunks_json)), 0) as count FROM files WHERE ${clause} AND chunks_json IS NOT NULL`);
|
|
732
|
+
const chunksCount2 = chunksByDirStmt.get(...params);
|
|
654
733
|
const errorsByDirStmt = sqlite.db.prepare(`
|
|
655
734
|
SELECT errors_json FROM files
|
|
656
735
|
WHERE ${clause} AND errors_json IS NOT NULL
|
|
@@ -668,8 +747,8 @@ async function getIndexStatus() {
|
|
|
668
747
|
return {
|
|
669
748
|
path: row.path,
|
|
670
749
|
status: row.status,
|
|
671
|
-
filesCount:
|
|
672
|
-
chunksCount:
|
|
750
|
+
filesCount: filesCount2.count,
|
|
751
|
+
chunksCount: chunksCount2.count,
|
|
673
752
|
lastIndexed: row.indexed_at && row.indexed_at > 0 ? new Date(row.indexed_at).toISOString() : null,
|
|
674
753
|
errors: dirErrorsList
|
|
675
754
|
};
|
|
@@ -715,6 +794,8 @@ export {
|
|
|
715
794
|
clearDatabase,
|
|
716
795
|
clearVectorCollection,
|
|
717
796
|
closeAllStorage,
|
|
797
|
+
closeSharedStorageForPath,
|
|
798
|
+
ensureCollectionDimensions,
|
|
718
799
|
getIndexStatus,
|
|
719
800
|
getResetPreview,
|
|
720
801
|
initDatabase,
|