chattercatcher 0.1.6 → 0.1.8
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/cli.js +649 -277
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +15 -6
- package/dist/index.js +367 -270
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,150 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/config/paths.ts
|
|
12
|
+
import os2 from "os";
|
|
13
|
+
import path2 from "path";
|
|
14
|
+
function getChatterCatcherHome() {
|
|
15
|
+
return process.env.CHATTERCATCHER_HOME || path2.join(os2.homedir(), ".chattercatcher");
|
|
16
|
+
}
|
|
17
|
+
function resolveHomePath(value) {
|
|
18
|
+
if (value === "~") {
|
|
19
|
+
return os2.homedir();
|
|
20
|
+
}
|
|
21
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
22
|
+
return path2.join(os2.homedir(), value.slice(2));
|
|
23
|
+
}
|
|
24
|
+
return path2.resolve(value);
|
|
25
|
+
}
|
|
26
|
+
function getConfigPath() {
|
|
27
|
+
return path2.join(getChatterCatcherHome(), "config.json");
|
|
28
|
+
}
|
|
29
|
+
function getSecretsPath() {
|
|
30
|
+
return path2.join(getChatterCatcherHome(), "secrets.json");
|
|
31
|
+
}
|
|
32
|
+
var init_paths = __esm({
|
|
33
|
+
"src/config/paths.ts"() {
|
|
34
|
+
"use strict";
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// src/rag/lancedb-store.ts
|
|
39
|
+
var lancedb_store_exports = {};
|
|
40
|
+
__export(lancedb_store_exports, {
|
|
41
|
+
LanceDbVectorStore: () => LanceDbVectorStore,
|
|
42
|
+
getLanceDbPath: () => getLanceDbPath
|
|
43
|
+
});
|
|
44
|
+
import fs6 from "fs/promises";
|
|
45
|
+
import path9 from "path";
|
|
46
|
+
function getLanceDbPath(config) {
|
|
47
|
+
return path9.join(resolveHomePath(config.storage.dataDir), "vector", "lancedb");
|
|
48
|
+
}
|
|
49
|
+
function toRow(record) {
|
|
50
|
+
return {
|
|
51
|
+
id: record.id,
|
|
52
|
+
vector: record.vector,
|
|
53
|
+
text: record.evidence.text,
|
|
54
|
+
source_json: JSON.stringify(record.evidence.source)
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function toLanceData(rows) {
|
|
58
|
+
return rows.map((row) => ({
|
|
59
|
+
id: row.id,
|
|
60
|
+
vector: row.vector,
|
|
61
|
+
text: row.text,
|
|
62
|
+
source_json: row.source_json
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
function escapeSqlString(value) {
|
|
66
|
+
return value.replace(/'/g, "''");
|
|
67
|
+
}
|
|
68
|
+
function toEvidence(row) {
|
|
69
|
+
const distance = row._distance ?? 0;
|
|
70
|
+
const vectorScore = 1 / (1 + Math.max(0, distance));
|
|
71
|
+
return {
|
|
72
|
+
id: row.id,
|
|
73
|
+
text: row.text,
|
|
74
|
+
score: vectorScore,
|
|
75
|
+
vectorScore,
|
|
76
|
+
source: JSON.parse(row.source_json)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
var DEFAULT_TABLE_NAME, LanceDbVectorStore;
|
|
80
|
+
var init_lancedb_store = __esm({
|
|
81
|
+
"src/rag/lancedb-store.ts"() {
|
|
82
|
+
"use strict";
|
|
83
|
+
init_paths();
|
|
84
|
+
DEFAULT_TABLE_NAME = "message_chunks";
|
|
85
|
+
LanceDbVectorStore = class _LanceDbVectorStore {
|
|
86
|
+
constructor(connection, tableName) {
|
|
87
|
+
this.connection = connection;
|
|
88
|
+
this.tableName = tableName;
|
|
89
|
+
}
|
|
90
|
+
connection;
|
|
91
|
+
tableName;
|
|
92
|
+
static async connect(uri, tableName = DEFAULT_TABLE_NAME) {
|
|
93
|
+
await fs6.mkdir(uri, { recursive: true });
|
|
94
|
+
const lancedb = await import("@lancedb/lancedb");
|
|
95
|
+
const connection = await lancedb.connect(uri);
|
|
96
|
+
return new _LanceDbVectorStore(connection, tableName);
|
|
97
|
+
}
|
|
98
|
+
static async connectFromConfig(config, tableName = DEFAULT_TABLE_NAME) {
|
|
99
|
+
return _LanceDbVectorStore.connect(getLanceDbPath(config), tableName);
|
|
100
|
+
}
|
|
101
|
+
close() {
|
|
102
|
+
this.connection.close();
|
|
103
|
+
}
|
|
104
|
+
async upsert(records) {
|
|
105
|
+
if (records.length === 0) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const rows = records.map(toRow);
|
|
109
|
+
const data = toLanceData(rows);
|
|
110
|
+
const table = await this.ensureTable(data);
|
|
111
|
+
const ids = rows.map((row) => `'${escapeSqlString(row.id)}'`).join(", ");
|
|
112
|
+
await table.delete(`id IN (${ids})`);
|
|
113
|
+
await table.add(data);
|
|
114
|
+
}
|
|
115
|
+
async search(vector, limit) {
|
|
116
|
+
const table = await this.openTableIfExists();
|
|
117
|
+
if (!table) {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
const rows = await table.vectorSearch(vector).limit(limit).toArray();
|
|
121
|
+
return rows.map(toEvidence);
|
|
122
|
+
}
|
|
123
|
+
async count() {
|
|
124
|
+
const table = await this.openTableIfExists();
|
|
125
|
+
if (!table) {
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
return table.countRows();
|
|
129
|
+
}
|
|
130
|
+
async ensureTable(initialRows) {
|
|
131
|
+
const table = await this.openTableIfExists();
|
|
132
|
+
if (table) {
|
|
133
|
+
return table;
|
|
134
|
+
}
|
|
135
|
+
return this.connection.createTable(this.tableName, initialRows);
|
|
136
|
+
}
|
|
137
|
+
async openTableIfExists() {
|
|
138
|
+
const tableNames = await this.connection.tableNames();
|
|
139
|
+
if (!tableNames.includes(this.tableName)) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
return this.connection.openTable(this.tableName);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
1
148
|
// src/config/schema.ts
|
|
2
149
|
import os from "os";
|
|
3
150
|
import path from "path";
|
|
@@ -64,30 +211,7 @@ function createDefaultSecrets() {
|
|
|
64
211
|
// src/config/store.ts
|
|
65
212
|
import fs from "fs/promises";
|
|
66
213
|
import path3 from "path";
|
|
67
|
-
|
|
68
|
-
// src/config/paths.ts
|
|
69
|
-
import os2 from "os";
|
|
70
|
-
import path2 from "path";
|
|
71
|
-
function getChatterCatcherHome() {
|
|
72
|
-
return process.env.CHATTERCATCHER_HOME || path2.join(os2.homedir(), ".chattercatcher");
|
|
73
|
-
}
|
|
74
|
-
function resolveHomePath(value) {
|
|
75
|
-
if (value === "~") {
|
|
76
|
-
return os2.homedir();
|
|
77
|
-
}
|
|
78
|
-
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
79
|
-
return path2.join(os2.homedir(), value.slice(2));
|
|
80
|
-
}
|
|
81
|
-
return path2.resolve(value);
|
|
82
|
-
}
|
|
83
|
-
function getConfigPath() {
|
|
84
|
-
return path2.join(getChatterCatcherHome(), "config.json");
|
|
85
|
-
}
|
|
86
|
-
function getSecretsPath() {
|
|
87
|
-
return path2.join(getChatterCatcherHome(), "secrets.json");
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// src/config/store.ts
|
|
214
|
+
init_paths();
|
|
91
215
|
async function readJsonFile(filePath, fallback) {
|
|
92
216
|
try {
|
|
93
217
|
const raw = await fs.readFile(filePath, "utf8");
|
|
@@ -151,6 +275,7 @@ function resolveEmbeddingApiKey(input) {
|
|
|
151
275
|
}
|
|
152
276
|
|
|
153
277
|
// src/data/deletion.ts
|
|
278
|
+
init_paths();
|
|
154
279
|
import fs2 from "fs/promises";
|
|
155
280
|
import path4 from "path";
|
|
156
281
|
function emptyResult(targetType, targetId) {
|
|
@@ -276,6 +401,7 @@ async function deleteLocalData(input) {
|
|
|
276
401
|
}
|
|
277
402
|
|
|
278
403
|
// src/db/database.ts
|
|
404
|
+
init_paths();
|
|
279
405
|
import Database from "better-sqlite3";
|
|
280
406
|
import fs3 from "fs";
|
|
281
407
|
import path5 from "path";
|
|
@@ -336,13 +462,6 @@ function migrateDatabase(database) {
|
|
|
336
462
|
tokenize = 'unicode61'
|
|
337
463
|
);
|
|
338
464
|
|
|
339
|
-
CREATE TABLE IF NOT EXISTS message_chunk_vectors (
|
|
340
|
-
chunk_id TEXT PRIMARY KEY REFERENCES message_chunks(id) ON DELETE CASCADE,
|
|
341
|
-
vector_json TEXT NOT NULL,
|
|
342
|
-
evidence_json TEXT NOT NULL,
|
|
343
|
-
updated_at TEXT NOT NULL
|
|
344
|
-
);
|
|
345
|
-
|
|
346
465
|
CREATE TABLE IF NOT EXISTS file_jobs (
|
|
347
466
|
id TEXT PRIMARY KEY,
|
|
348
467
|
source_path TEXT NOT NULL,
|
|
@@ -362,7 +481,8 @@ function migrateDatabase(database) {
|
|
|
362
481
|
}
|
|
363
482
|
|
|
364
483
|
// src/doctor/checks.ts
|
|
365
|
-
|
|
484
|
+
init_paths();
|
|
485
|
+
import fs7 from "fs/promises";
|
|
366
486
|
|
|
367
487
|
// src/files/jobs.ts
|
|
368
488
|
import crypto from "crypto";
|
|
@@ -504,10 +624,120 @@ var FileJobRepository = class {
|
|
|
504
624
|
};
|
|
505
625
|
|
|
506
626
|
// src/gateway/runtime.ts
|
|
507
|
-
|
|
627
|
+
init_paths();
|
|
628
|
+
import fs5 from "fs";
|
|
629
|
+
import path8 from "path";
|
|
630
|
+
|
|
631
|
+
// src/logs/reader.ts
|
|
632
|
+
init_paths();
|
|
633
|
+
import fs4 from "fs/promises";
|
|
634
|
+
import { watch } from "fs";
|
|
508
635
|
import path7 from "path";
|
|
636
|
+
function getLogsDirectory() {
|
|
637
|
+
return path7.join(getChatterCatcherHome(), "logs");
|
|
638
|
+
}
|
|
639
|
+
function resolveLogPath(fileName, logsDir = getLogsDirectory()) {
|
|
640
|
+
return path7.isAbsolute(fileName) ? fileName : path7.join(logsDir, fileName);
|
|
641
|
+
}
|
|
642
|
+
function normalizeLineCount(value, fallback = 200) {
|
|
643
|
+
const parsed = Number(value ?? fallback);
|
|
644
|
+
return Number.isFinite(parsed) ? Math.min(Math.max(Math.trunc(parsed), 1), 1e4) : fallback;
|
|
645
|
+
}
|
|
646
|
+
async function listLogFiles(logsDir = getLogsDirectory()) {
|
|
647
|
+
let entries;
|
|
648
|
+
try {
|
|
649
|
+
entries = await fs4.readdir(logsDir, { withFileTypes: true });
|
|
650
|
+
} catch (error) {
|
|
651
|
+
if (error.code === "ENOENT") {
|
|
652
|
+
return [];
|
|
653
|
+
}
|
|
654
|
+
throw error;
|
|
655
|
+
}
|
|
656
|
+
const files = await Promise.all(
|
|
657
|
+
entries.filter((entry) => entry.isFile() && entry.name.endsWith(".log")).map(async (entry) => {
|
|
658
|
+
const filePath = path7.join(logsDir, entry.name);
|
|
659
|
+
const stats = await fs4.stat(filePath);
|
|
660
|
+
return {
|
|
661
|
+
name: entry.name,
|
|
662
|
+
path: filePath,
|
|
663
|
+
updatedAt: stats.mtime,
|
|
664
|
+
bytes: stats.size
|
|
665
|
+
};
|
|
666
|
+
})
|
|
667
|
+
);
|
|
668
|
+
return files.sort((left, right) => right.updatedAt.getTime() - left.updatedAt.getTime());
|
|
669
|
+
}
|
|
670
|
+
function tailLines(content, lines) {
|
|
671
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
672
|
+
const parts = normalized.endsWith("\n") ? normalized.slice(0, -1).split("\n") : normalized.split("\n");
|
|
673
|
+
return parts.slice(-lines).join("\n");
|
|
674
|
+
}
|
|
675
|
+
async function readLogTail(input) {
|
|
676
|
+
const stats = await fs4.stat(input.filePath);
|
|
677
|
+
const content = await fs4.readFile(input.filePath, "utf8");
|
|
678
|
+
return {
|
|
679
|
+
file: {
|
|
680
|
+
name: path7.basename(input.filePath),
|
|
681
|
+
path: input.filePath,
|
|
682
|
+
updatedAt: stats.mtime,
|
|
683
|
+
bytes: stats.size
|
|
684
|
+
},
|
|
685
|
+
content: tailLines(content, normalizeLineCount(input.lines))
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
async function readLatestLogTail(input = {}) {
|
|
689
|
+
if (input.fileName) {
|
|
690
|
+
return readLogTail({
|
|
691
|
+
filePath: resolveLogPath(input.fileName, input.logsDir),
|
|
692
|
+
lines: input.lines
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
const [latest] = await listLogFiles(input.logsDir);
|
|
696
|
+
if (!latest) {
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
return readLogTail({ filePath: latest.path, lines: input.lines });
|
|
700
|
+
}
|
|
701
|
+
async function followLogFile(input) {
|
|
702
|
+
let offset = (await fs4.stat(input.filePath)).size;
|
|
703
|
+
const directory = path7.dirname(input.filePath);
|
|
704
|
+
const fileName = path7.basename(input.filePath);
|
|
705
|
+
async function readAppended() {
|
|
706
|
+
const stats = await fs4.stat(input.filePath);
|
|
707
|
+
if (stats.size < offset) {
|
|
708
|
+
offset = 0;
|
|
709
|
+
}
|
|
710
|
+
if (stats.size === offset) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
const handle = await fs4.open(input.filePath, "r");
|
|
714
|
+
try {
|
|
715
|
+
const length = stats.size - offset;
|
|
716
|
+
const buffer = Buffer.alloc(length);
|
|
717
|
+
await handle.read(buffer, 0, length, offset);
|
|
718
|
+
offset = stats.size;
|
|
719
|
+
input.onChunk(buffer.toString("utf8"));
|
|
720
|
+
} finally {
|
|
721
|
+
await handle.close();
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
const watcher = watch(directory, (eventType, changedFileName) => {
|
|
725
|
+
if (eventType !== "change" || changedFileName?.toString() !== fileName) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
void readAppended().catch((error) => {
|
|
729
|
+
input.onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
730
|
+
});
|
|
731
|
+
});
|
|
732
|
+
return () => watcher.close();
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// src/gateway/runtime.ts
|
|
509
736
|
function getGatewayPidPath() {
|
|
510
|
-
return
|
|
737
|
+
return path8.join(getChatterCatcherHome(), "gateway.pid");
|
|
738
|
+
}
|
|
739
|
+
function getGatewayLogPath() {
|
|
740
|
+
return path8.join(getLogsDirectory(), "gateway.log");
|
|
511
741
|
}
|
|
512
742
|
function isProcessRunning(pid) {
|
|
513
743
|
if (!Number.isInteger(pid) || pid <= 0) {
|
|
@@ -522,7 +752,7 @@ function isProcessRunning(pid) {
|
|
|
522
752
|
}
|
|
523
753
|
function readGatewayPidRecord(pidFile = getGatewayPidPath()) {
|
|
524
754
|
try {
|
|
525
|
-
const raw =
|
|
755
|
+
const raw = fs5.readFileSync(pidFile, "utf8");
|
|
526
756
|
const parsed = JSON.parse(raw);
|
|
527
757
|
if (!Number.isInteger(parsed.pid) || typeof parsed.startedAt !== "string" || typeof parsed.command !== "string") {
|
|
528
758
|
return null;
|
|
@@ -534,7 +764,9 @@ function readGatewayPidRecord(pidFile = getGatewayPidPath()) {
|
|
|
534
764
|
return {
|
|
535
765
|
pid,
|
|
536
766
|
startedAt: parsed.startedAt,
|
|
537
|
-
command: parsed.command
|
|
767
|
+
command: parsed.command,
|
|
768
|
+
...typeof parsed.logFile === "string" ? { logFile: parsed.logFile } : {},
|
|
769
|
+
...parsed.mode === "gateway" || parsed.mode === "web" ? { mode: parsed.mode } : {}
|
|
538
770
|
};
|
|
539
771
|
} catch (error) {
|
|
540
772
|
if (error.code === "ENOENT") {
|
|
@@ -548,13 +780,13 @@ function writeGatewayPidRecord(pidFile = getGatewayPidPath(), record = {
|
|
|
548
780
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
549
781
|
command: process.argv.join(" ")
|
|
550
782
|
}) {
|
|
551
|
-
|
|
552
|
-
|
|
783
|
+
fs5.mkdirSync(path8.dirname(pidFile), { recursive: true });
|
|
784
|
+
fs5.writeFileSync(pidFile, `${JSON.stringify(record, null, 2)}
|
|
553
785
|
`, "utf8");
|
|
554
786
|
}
|
|
555
787
|
function removeGatewayPidRecord(pidFile = getGatewayPidPath()) {
|
|
556
788
|
try {
|
|
557
|
-
|
|
789
|
+
fs5.rmSync(pidFile, { force: true });
|
|
558
790
|
} catch {
|
|
559
791
|
}
|
|
560
792
|
}
|
|
@@ -606,6 +838,28 @@ function stopGatewayProcess(pidFile = getGatewayPidPath()) {
|
|
|
606
838
|
|
|
607
839
|
// src/gateway/index.ts
|
|
608
840
|
function getGatewayStatus(config, secrets) {
|
|
841
|
+
const runtime = getGatewayRuntimeState();
|
|
842
|
+
const configured = Boolean(config.feishu.appId && (!secrets || secrets.feishu.appSecret));
|
|
843
|
+
if (runtime.running && runtime.record) {
|
|
844
|
+
if (runtime.record.mode === "web" && !configured) {
|
|
845
|
+
return {
|
|
846
|
+
configured,
|
|
847
|
+
connection: "running",
|
|
848
|
+
message: `\u672C\u5730 Web UI \u8FDB\u7A0B\u6B63\u5728\u8FD0\u884C\uFF1Apid=${runtime.record.pid}\uFF0CstartedAt=${runtime.record.startedAt}\uFF1B\u98DE\u4E66\u914D\u7F6E\u5C1A\u672A\u5B8C\u6210\u3002`,
|
|
849
|
+
pid: runtime.record.pid,
|
|
850
|
+
pidFile: runtime.pidFile,
|
|
851
|
+
logFile: runtime.record.logFile
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
return {
|
|
855
|
+
configured: true,
|
|
856
|
+
connection: "running",
|
|
857
|
+
message: `\u98DE\u4E66 Gateway \u6B63\u5728\u8FD0\u884C\uFF1Apid=${runtime.record.pid}\uFF0CstartedAt=${runtime.record.startedAt}`,
|
|
858
|
+
pid: runtime.record.pid,
|
|
859
|
+
pidFile: runtime.pidFile,
|
|
860
|
+
logFile: runtime.record.logFile
|
|
861
|
+
};
|
|
862
|
+
}
|
|
609
863
|
if (!config.feishu.appId) {
|
|
610
864
|
return {
|
|
611
865
|
configured: false,
|
|
@@ -620,23 +874,14 @@ function getGatewayStatus(config, secrets) {
|
|
|
620
874
|
message: "\u5C1A\u672A\u914D\u7F6E\u98DE\u4E66 App Secret\u3002\u8BF7\u8FD0\u884C chattercatcher setup \u6216 chattercatcher settings\u3002"
|
|
621
875
|
};
|
|
622
876
|
}
|
|
623
|
-
const runtime = getGatewayRuntimeState();
|
|
624
|
-
if (runtime.running && runtime.record) {
|
|
625
|
-
return {
|
|
626
|
-
configured: true,
|
|
627
|
-
connection: "running",
|
|
628
|
-
message: `\u98DE\u4E66 Gateway \u6B63\u5728\u8FD0\u884C\uFF1Apid=${runtime.record.pid}\uFF0CstartedAt=${runtime.record.startedAt}`,
|
|
629
|
-
pid: runtime.record.pid,
|
|
630
|
-
pidFile: runtime.pidFile
|
|
631
|
-
};
|
|
632
|
-
}
|
|
633
877
|
if (runtime.stale && runtime.record) {
|
|
634
878
|
return {
|
|
635
879
|
configured: true,
|
|
636
880
|
connection: "ready_for_start",
|
|
637
881
|
message: `\u98DE\u4E66\u957F\u8FDE\u63A5\u914D\u7F6E\u5DF2\u5C31\u7EEA\uFF1B\u53D1\u73B0\u8FC7\u671F PID \u6587\u4EF6\uFF1Apid=${runtime.record.pid}\u3002\u8FD0\u884C chattercatcher gateway start \u4F1A\u8986\u76D6\u8FD0\u884C\u8BB0\u5F55\u3002`,
|
|
638
882
|
pid: runtime.record.pid,
|
|
639
|
-
pidFile: runtime.pidFile
|
|
883
|
+
pidFile: runtime.pidFile,
|
|
884
|
+
logFile: runtime.record.logFile
|
|
640
885
|
};
|
|
641
886
|
}
|
|
642
887
|
return {
|
|
@@ -1133,82 +1378,6 @@ var MessageFtsRetriever = class {
|
|
|
1133
1378
|
}
|
|
1134
1379
|
};
|
|
1135
1380
|
|
|
1136
|
-
// src/rag/embedding.ts
|
|
1137
|
-
function cosineSimilarity(left, right) {
|
|
1138
|
-
if (left.length === 0 || right.length === 0 || left.length !== right.length) {
|
|
1139
|
-
return 0;
|
|
1140
|
-
}
|
|
1141
|
-
let dot = 0;
|
|
1142
|
-
let leftNorm = 0;
|
|
1143
|
-
let rightNorm = 0;
|
|
1144
|
-
for (let index = 0; index < left.length; index += 1) {
|
|
1145
|
-
const leftValue = left[index] ?? 0;
|
|
1146
|
-
const rightValue = right[index] ?? 0;
|
|
1147
|
-
dot += leftValue * rightValue;
|
|
1148
|
-
leftNorm += leftValue * leftValue;
|
|
1149
|
-
rightNorm += rightValue * rightValue;
|
|
1150
|
-
}
|
|
1151
|
-
if (leftNorm === 0 || rightNorm === 0) {
|
|
1152
|
-
return 0;
|
|
1153
|
-
}
|
|
1154
|
-
return dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
// src/rag/sqlite-vector-store.ts
|
|
1158
|
-
var SQLiteVectorStore = class {
|
|
1159
|
-
constructor(database) {
|
|
1160
|
-
this.database = database;
|
|
1161
|
-
}
|
|
1162
|
-
database;
|
|
1163
|
-
async upsert(records) {
|
|
1164
|
-
if (records.length === 0) {
|
|
1165
|
-
return;
|
|
1166
|
-
}
|
|
1167
|
-
const statement = this.database.prepare(`
|
|
1168
|
-
INSERT INTO message_chunk_vectors (chunk_id, vector_json, evidence_json, updated_at)
|
|
1169
|
-
VALUES (@chunkId, @vectorJson, @evidenceJson, @updatedAt)
|
|
1170
|
-
ON CONFLICT(chunk_id) DO UPDATE SET
|
|
1171
|
-
vector_json = excluded.vector_json,
|
|
1172
|
-
evidence_json = excluded.evidence_json,
|
|
1173
|
-
updated_at = excluded.updated_at
|
|
1174
|
-
`);
|
|
1175
|
-
const upsertMany = this.database.transaction((items) => {
|
|
1176
|
-
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1177
|
-
for (const item of items) {
|
|
1178
|
-
statement.run({
|
|
1179
|
-
chunkId: item.id,
|
|
1180
|
-
vectorJson: JSON.stringify(item.vector),
|
|
1181
|
-
evidenceJson: JSON.stringify(item.evidence),
|
|
1182
|
-
updatedAt
|
|
1183
|
-
});
|
|
1184
|
-
}
|
|
1185
|
-
});
|
|
1186
|
-
upsertMany(records);
|
|
1187
|
-
}
|
|
1188
|
-
async search(vector, limit) {
|
|
1189
|
-
if (limit <= 0) {
|
|
1190
|
-
return [];
|
|
1191
|
-
}
|
|
1192
|
-
const rows = this.database.prepare("SELECT chunk_id, vector_json, evidence_json FROM message_chunk_vectors").all();
|
|
1193
|
-
return rows.map((row) => {
|
|
1194
|
-
const storedVector = JSON.parse(row.vector_json);
|
|
1195
|
-
const evidence = JSON.parse(row.evidence_json);
|
|
1196
|
-
const vectorScore = cosineSimilarity(vector, storedVector);
|
|
1197
|
-
return {
|
|
1198
|
-
...evidence,
|
|
1199
|
-
score: vectorScore,
|
|
1200
|
-
vectorScore
|
|
1201
|
-
};
|
|
1202
|
-
}).sort((left, right) => right.vectorScore - left.vectorScore).slice(0, limit);
|
|
1203
|
-
}
|
|
1204
|
-
async count() {
|
|
1205
|
-
const row = this.database.prepare("SELECT COUNT(*) AS count FROM message_chunk_vectors").get();
|
|
1206
|
-
return row.count;
|
|
1207
|
-
}
|
|
1208
|
-
close() {
|
|
1209
|
-
}
|
|
1210
|
-
};
|
|
1211
|
-
|
|
1212
1381
|
// src/rag/vector-retriever.ts
|
|
1213
1382
|
var VectorRetriever = class {
|
|
1214
1383
|
constructor(embedding, store, limit = 8) {
|
|
@@ -1233,7 +1402,8 @@ async function createHybridRetriever(input) {
|
|
|
1233
1402
|
const retrievers = [new MessageFtsRetriever(input.messages, { excludeMessageIds: input.excludeMessageIds })];
|
|
1234
1403
|
const closers = [];
|
|
1235
1404
|
if (hasEmbeddingConfig(input.config, input.secrets)) {
|
|
1236
|
-
const
|
|
1405
|
+
const { LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
|
|
1406
|
+
const vectorStore = await LanceDbVectorStore2.connectFromConfig(input.config);
|
|
1237
1407
|
retrievers.push(new VectorRetriever(createEmbeddingModel(input.config, input.secrets), vectorStore));
|
|
1238
1408
|
closers.push(() => vectorStore.close());
|
|
1239
1409
|
}
|
|
@@ -1265,7 +1435,7 @@ async function runDoctor(config, secrets, options = {}) {
|
|
|
1265
1435
|
checks.push(checkEmbeddingConfig(config, secrets));
|
|
1266
1436
|
checks.push(await checkSqlite(config));
|
|
1267
1437
|
checks.push(await checkFilePipeline(config));
|
|
1268
|
-
checks.push(await
|
|
1438
|
+
checks.push(await checkLanceDb(config));
|
|
1269
1439
|
checks.push(checkRagPolicy());
|
|
1270
1440
|
if (options.online) {
|
|
1271
1441
|
checks.push(await checkChatModel(config, secrets));
|
|
@@ -1276,8 +1446,8 @@ async function runDoctor(config, secrets, options = {}) {
|
|
|
1276
1446
|
async function checkHomeDirectory() {
|
|
1277
1447
|
const home = getChatterCatcherHome();
|
|
1278
1448
|
try {
|
|
1279
|
-
await
|
|
1280
|
-
await
|
|
1449
|
+
await fs7.mkdir(home, { recursive: true });
|
|
1450
|
+
await fs7.access(home);
|
|
1281
1451
|
return pass("\u914D\u7F6E\u76EE\u5F55", home);
|
|
1282
1452
|
} catch (error) {
|
|
1283
1453
|
return fail("\u914D\u7F6E\u76EE\u5F55", error instanceof Error ? error.message : String(error));
|
|
@@ -1298,7 +1468,7 @@ function checkLlmConfig(config, secrets) {
|
|
|
1298
1468
|
}
|
|
1299
1469
|
function checkEmbeddingConfig(config, secrets) {
|
|
1300
1470
|
if (!hasEmbeddingConfig(config, secrets)) {
|
|
1301
|
-
return warn("Embedding \u914D\u7F6E", "\u672A\u914D\u7F6E\u5B8C\u6574\uFF1BRAG \u4F1A\u4F7F\u7528 SQLite FTS\uFF0C\u65E0\u6CD5\u4F7F\u7528
|
|
1471
|
+
return warn("Embedding \u914D\u7F6E", "\u672A\u914D\u7F6E\u5B8C\u6574\uFF1BRAG \u4F1A\u4F7F\u7528 SQLite FTS\uFF0C\u65E0\u6CD5\u4F7F\u7528 LanceDB \u8BED\u4E49\u68C0\u7D22\u3002");
|
|
1302
1472
|
}
|
|
1303
1473
|
return pass("Embedding \u914D\u7F6E", `${config.embedding.model} @ ${config.embedding.baseUrl || config.llm.baseUrl}`);
|
|
1304
1474
|
}
|
|
@@ -1332,17 +1502,17 @@ async function checkFilePipeline(config) {
|
|
|
1332
1502
|
database?.close();
|
|
1333
1503
|
}
|
|
1334
1504
|
}
|
|
1335
|
-
async function
|
|
1336
|
-
let
|
|
1505
|
+
async function checkLanceDb(config) {
|
|
1506
|
+
let store = null;
|
|
1337
1507
|
try {
|
|
1338
|
-
|
|
1339
|
-
|
|
1508
|
+
const { getLanceDbPath: getLanceDbPath2, LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
|
|
1509
|
+
store = await LanceDbVectorStore2.connectFromConfig(config);
|
|
1340
1510
|
const count = await store.count();
|
|
1341
|
-
return pass("
|
|
1511
|
+
return pass("LanceDB", `${getLanceDbPath2(config)}\uFF1Bvectors=${count}`);
|
|
1342
1512
|
} catch (error) {
|
|
1343
|
-
return fail("
|
|
1513
|
+
return fail("LanceDB", error instanceof Error ? error.message : String(error));
|
|
1344
1514
|
} finally {
|
|
1345
|
-
|
|
1515
|
+
store?.close();
|
|
1346
1516
|
}
|
|
1347
1517
|
}
|
|
1348
1518
|
function checkRagPolicy() {
|
|
@@ -1383,8 +1553,9 @@ function formatDoctorChecks(checks) {
|
|
|
1383
1553
|
}
|
|
1384
1554
|
|
|
1385
1555
|
// src/export/data-export.ts
|
|
1386
|
-
|
|
1387
|
-
import
|
|
1556
|
+
init_paths();
|
|
1557
|
+
import fs8 from "fs/promises";
|
|
1558
|
+
import path10 from "path";
|
|
1388
1559
|
function parseJsonObject(value) {
|
|
1389
1560
|
try {
|
|
1390
1561
|
const parsed = JSON.parse(value);
|
|
@@ -1403,11 +1574,11 @@ function parseJsonArray(value) {
|
|
|
1403
1574
|
}
|
|
1404
1575
|
function defaultExportPath(config, exportedAt) {
|
|
1405
1576
|
const fileName = `chattercatcher-export-${exportedAt.replace(/[:.]/g, "-")}.json`;
|
|
1406
|
-
return
|
|
1577
|
+
return path10.join(resolveHomePath(config.storage.dataDir), "exports", fileName);
|
|
1407
1578
|
}
|
|
1408
1579
|
async function exportLocalData(input) {
|
|
1409
1580
|
const exportedAt = input.exportedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1410
|
-
const outputPath =
|
|
1581
|
+
const outputPath = path10.resolve(input.outputPath ?? defaultExportPath(input.config, exportedAt));
|
|
1411
1582
|
const chats = input.database.prepare(
|
|
1412
1583
|
`
|
|
1413
1584
|
SELECT
|
|
@@ -1494,8 +1665,8 @@ async function exportLocalData(input) {
|
|
|
1494
1665
|
fileJobs
|
|
1495
1666
|
}
|
|
1496
1667
|
};
|
|
1497
|
-
await
|
|
1498
|
-
await
|
|
1668
|
+
await fs8.mkdir(path10.dirname(outputPath), { recursive: true });
|
|
1669
|
+
await fs8.writeFile(outputPath, `${JSON.stringify(payload, null, 2)}
|
|
1499
1670
|
`, "utf8");
|
|
1500
1671
|
return {
|
|
1501
1672
|
outputPath,
|
|
@@ -1507,8 +1678,8 @@ async function exportLocalData(input) {
|
|
|
1507
1678
|
}
|
|
1508
1679
|
|
|
1509
1680
|
// src/export/data-restore.ts
|
|
1510
|
-
import
|
|
1511
|
-
import
|
|
1681
|
+
import fs9 from "fs/promises";
|
|
1682
|
+
import path11 from "path";
|
|
1512
1683
|
function asObject(value) {
|
|
1513
1684
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1514
1685
|
}
|
|
@@ -1556,8 +1727,8 @@ function clearDatabase(database) {
|
|
|
1556
1727
|
database.prepare("DELETE FROM chats").run();
|
|
1557
1728
|
}
|
|
1558
1729
|
async function restoreLocalData(input) {
|
|
1559
|
-
const inputPath =
|
|
1560
|
-
const payload = parsePayload(await
|
|
1730
|
+
const inputPath = path11.resolve(input.inputPath);
|
|
1731
|
+
const payload = parsePayload(await fs9.readFile(inputPath, "utf8"));
|
|
1561
1732
|
const mode = input.replace ? "replace" : "merge";
|
|
1562
1733
|
const restore = input.database.transaction(() => {
|
|
1563
1734
|
if (input.replace) {
|
|
@@ -2299,9 +2470,10 @@ function normalizeFeishuReceiveMessageEvent(payload) {
|
|
|
2299
2470
|
}
|
|
2300
2471
|
|
|
2301
2472
|
// src/feishu/resource-downloader.ts
|
|
2473
|
+
init_paths();
|
|
2302
2474
|
import * as lark3 from "@larksuiteoapi/node-sdk";
|
|
2303
|
-
import
|
|
2304
|
-
import
|
|
2475
|
+
import fs10 from "fs/promises";
|
|
2476
|
+
import path12 from "path";
|
|
2305
2477
|
var RESOURCE_TYPE_BY_KIND = {
|
|
2306
2478
|
file: "file",
|
|
2307
2479
|
image: "image",
|
|
@@ -2339,10 +2511,10 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
|
|
|
2339
2511
|
}
|
|
2340
2512
|
async download(input) {
|
|
2341
2513
|
const resourceType = RESOURCE_TYPE_BY_KIND[input.attachment.kind];
|
|
2342
|
-
const targetDir =
|
|
2343
|
-
await
|
|
2514
|
+
const targetDir = path12.join(this.dataDir, "files", "feishu");
|
|
2515
|
+
await fs10.mkdir(targetDir, { recursive: true });
|
|
2344
2516
|
const fileName = buildStoredFileName(input);
|
|
2345
|
-
const storedPath =
|
|
2517
|
+
const storedPath = path12.join(targetDir, fileName);
|
|
2346
2518
|
const payload = {
|
|
2347
2519
|
params: { type: resourceType },
|
|
2348
2520
|
path: { message_id: input.messageId, file_key: input.attachment.fileKey }
|
|
@@ -2364,30 +2536,31 @@ var FeishuResourceDownloader = class _FeishuResourceDownloader {
|
|
|
2364
2536
|
};
|
|
2365
2537
|
|
|
2366
2538
|
// src/files/ingest.ts
|
|
2539
|
+
init_paths();
|
|
2367
2540
|
import crypto3 from "crypto";
|
|
2368
|
-
import
|
|
2369
|
-
import
|
|
2541
|
+
import fs12 from "fs/promises";
|
|
2542
|
+
import path14 from "path";
|
|
2370
2543
|
|
|
2371
2544
|
// src/files/parser.ts
|
|
2372
|
-
import
|
|
2373
|
-
import
|
|
2545
|
+
import fs11 from "fs/promises";
|
|
2546
|
+
import path13 from "path";
|
|
2374
2547
|
import mammoth from "mammoth";
|
|
2375
2548
|
import { PDFParse } from "pdf-parse";
|
|
2376
2549
|
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".txt", ".md", ".markdown", ".json", ".csv", ".tsv", ".log"]);
|
|
2377
2550
|
var DOCX_EXTENSIONS = /* @__PURE__ */ new Set([".docx"]);
|
|
2378
2551
|
var PDF_EXTENSIONS = /* @__PURE__ */ new Set([".pdf"]);
|
|
2379
2552
|
function isSupportedParseFile(filePath) {
|
|
2380
|
-
const extension =
|
|
2553
|
+
const extension = path13.extname(filePath).toLowerCase();
|
|
2381
2554
|
return TEXT_EXTENSIONS.has(extension) || DOCX_EXTENSIONS.has(extension) || PDF_EXTENSIONS.has(extension);
|
|
2382
2555
|
}
|
|
2383
2556
|
function describeSupportedParseTypes() {
|
|
2384
2557
|
return "txt\u3001md\u3001json\u3001csv\u3001tsv\u3001log\u3001docx\u3001pdf";
|
|
2385
2558
|
}
|
|
2386
2559
|
async function parseFileToText(filePath) {
|
|
2387
|
-
const extension =
|
|
2560
|
+
const extension = path13.extname(filePath).toLowerCase();
|
|
2388
2561
|
if (TEXT_EXTENSIONS.has(extension)) {
|
|
2389
2562
|
return {
|
|
2390
|
-
text: await
|
|
2563
|
+
text: await fs11.readFile(filePath, "utf8"),
|
|
2391
2564
|
parser: "text",
|
|
2392
2565
|
warnings: []
|
|
2393
2566
|
};
|
|
@@ -2401,7 +2574,7 @@ async function parseFileToText(filePath) {
|
|
|
2401
2574
|
};
|
|
2402
2575
|
}
|
|
2403
2576
|
if (PDF_EXTENSIONS.has(extension)) {
|
|
2404
|
-
const buffer = await
|
|
2577
|
+
const buffer = await fs11.readFile(filePath);
|
|
2405
2578
|
const parser = new PDFParse({ data: buffer });
|
|
2406
2579
|
try {
|
|
2407
2580
|
const result = await parser.getText();
|
|
@@ -2423,7 +2596,7 @@ function isSupportedTextFile(filePath) {
|
|
|
2423
2596
|
}
|
|
2424
2597
|
function ensureSupportedTextFile(filePath) {
|
|
2425
2598
|
if (!isSupportedTextFile(filePath)) {
|
|
2426
|
-
const extension =
|
|
2599
|
+
const extension = path14.extname(filePath).toLowerCase();
|
|
2427
2600
|
throw new Error(`\u6682\u4E0D\u652F\u6301\u8BE5\u6587\u4EF6\u7C7B\u578B\uFF1A${extension || "\u65E0\u6269\u5C55\u540D"}\u3002\u5F53\u524D\u652F\u6301 ${describeSupportedParseTypes()}\u3002`);
|
|
2428
2601
|
}
|
|
2429
2602
|
}
|
|
@@ -2432,12 +2605,12 @@ function stableStoredName(sourcePath, fileName) {
|
|
|
2432
2605
|
return `${digest}-${fileName}`;
|
|
2433
2606
|
}
|
|
2434
2607
|
async function ingestLocalFile(input) {
|
|
2435
|
-
const sourcePath =
|
|
2436
|
-
const fileName =
|
|
2608
|
+
const sourcePath = path14.resolve(input.filePath);
|
|
2609
|
+
const fileName = path14.basename(sourcePath);
|
|
2437
2610
|
const jobId = input.jobs?.start({ sourcePath, fileName });
|
|
2438
2611
|
try {
|
|
2439
2612
|
ensureSupportedTextFile(sourcePath);
|
|
2440
|
-
const stat = await
|
|
2613
|
+
const stat = await fs12.stat(sourcePath);
|
|
2441
2614
|
if (!stat.isFile()) {
|
|
2442
2615
|
throw new Error(`\u4E0D\u662F\u6587\u4EF6\uFF1A${sourcePath}`);
|
|
2443
2616
|
}
|
|
@@ -2446,10 +2619,10 @@ async function ingestLocalFile(input) {
|
|
|
2446
2619
|
if (!text) {
|
|
2447
2620
|
throw new Error(`\u6587\u4EF6\u6CA1\u6709\u53EF\u7D22\u5F15\u6587\u672C\uFF1A${sourcePath}`);
|
|
2448
2621
|
}
|
|
2449
|
-
const fileDir =
|
|
2450
|
-
await
|
|
2451
|
-
const storedPath =
|
|
2452
|
-
await
|
|
2622
|
+
const fileDir = path14.join(resolveHomePath(input.config.storage.dataDir), "files");
|
|
2623
|
+
await fs12.mkdir(fileDir, { recursive: true });
|
|
2624
|
+
const storedPath = path14.join(fileDir, stableStoredName(sourcePath, fileName));
|
|
2625
|
+
await fs12.copyFile(sourcePath, storedPath);
|
|
2453
2626
|
const messageId = input.messages.ingest({
|
|
2454
2627
|
platform: "local-file",
|
|
2455
2628
|
platformChatId: "local-files",
|
|
@@ -2580,107 +2753,25 @@ var GatewayIngestor = class {
|
|
|
2580
2753
|
}
|
|
2581
2754
|
};
|
|
2582
2755
|
|
|
2583
|
-
// src/
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
function getLogsDirectory() {
|
|
2588
|
-
return path13.join(getChatterCatcherHome(), "logs");
|
|
2589
|
-
}
|
|
2590
|
-
function resolveLogPath(fileName, logsDir = getLogsDirectory()) {
|
|
2591
|
-
return path13.isAbsolute(fileName) ? fileName : path13.join(logsDir, fileName);
|
|
2592
|
-
}
|
|
2593
|
-
function normalizeLineCount(value, fallback = 200) {
|
|
2594
|
-
const parsed = Number(value ?? fallback);
|
|
2595
|
-
return Number.isFinite(parsed) ? Math.min(Math.max(Math.trunc(parsed), 1), 1e4) : fallback;
|
|
2596
|
-
}
|
|
2597
|
-
async function listLogFiles(logsDir = getLogsDirectory()) {
|
|
2598
|
-
let entries;
|
|
2599
|
-
try {
|
|
2600
|
-
entries = await fs11.readdir(logsDir, { withFileTypes: true });
|
|
2601
|
-
} catch (error) {
|
|
2602
|
-
if (error.code === "ENOENT") {
|
|
2603
|
-
return [];
|
|
2604
|
-
}
|
|
2605
|
-
throw error;
|
|
2606
|
-
}
|
|
2607
|
-
const files = await Promise.all(
|
|
2608
|
-
entries.filter((entry) => entry.isFile() && entry.name.endsWith(".log")).map(async (entry) => {
|
|
2609
|
-
const filePath = path13.join(logsDir, entry.name);
|
|
2610
|
-
const stats = await fs11.stat(filePath);
|
|
2611
|
-
return {
|
|
2612
|
-
name: entry.name,
|
|
2613
|
-
path: filePath,
|
|
2614
|
-
updatedAt: stats.mtime,
|
|
2615
|
-
bytes: stats.size
|
|
2616
|
-
};
|
|
2617
|
-
})
|
|
2618
|
-
);
|
|
2619
|
-
return files.sort((left, right) => right.updatedAt.getTime() - left.updatedAt.getTime());
|
|
2620
|
-
}
|
|
2621
|
-
function tailLines(content, lines) {
|
|
2622
|
-
const normalized = content.replace(/\r\n/g, "\n");
|
|
2623
|
-
const parts = normalized.endsWith("\n") ? normalized.slice(0, -1).split("\n") : normalized.split("\n");
|
|
2624
|
-
return parts.slice(-lines).join("\n");
|
|
2625
|
-
}
|
|
2626
|
-
async function readLogTail(input) {
|
|
2627
|
-
const stats = await fs11.stat(input.filePath);
|
|
2628
|
-
const content = await fs11.readFile(input.filePath, "utf8");
|
|
2629
|
-
return {
|
|
2630
|
-
file: {
|
|
2631
|
-
name: path13.basename(input.filePath),
|
|
2632
|
-
path: input.filePath,
|
|
2633
|
-
updatedAt: stats.mtime,
|
|
2634
|
-
bytes: stats.size
|
|
2635
|
-
},
|
|
2636
|
-
content: tailLines(content, normalizeLineCount(input.lines))
|
|
2637
|
-
};
|
|
2638
|
-
}
|
|
2639
|
-
async function readLatestLogTail(input = {}) {
|
|
2640
|
-
if (input.fileName) {
|
|
2641
|
-
return readLogTail({
|
|
2642
|
-
filePath: resolveLogPath(input.fileName, input.logsDir),
|
|
2643
|
-
lines: input.lines
|
|
2644
|
-
});
|
|
2756
|
+
// src/rag/embedding.ts
|
|
2757
|
+
function cosineSimilarity(left, right) {
|
|
2758
|
+
if (left.length === 0 || right.length === 0 || left.length !== right.length) {
|
|
2759
|
+
return 0;
|
|
2645
2760
|
}
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2761
|
+
let dot = 0;
|
|
2762
|
+
let leftNorm = 0;
|
|
2763
|
+
let rightNorm = 0;
|
|
2764
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
2765
|
+
const leftValue = left[index] ?? 0;
|
|
2766
|
+
const rightValue = right[index] ?? 0;
|
|
2767
|
+
dot += leftValue * rightValue;
|
|
2768
|
+
leftNorm += leftValue * leftValue;
|
|
2769
|
+
rightNorm += rightValue * rightValue;
|
|
2649
2770
|
}
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
async function followLogFile(input) {
|
|
2653
|
-
let offset = (await fs11.stat(input.filePath)).size;
|
|
2654
|
-
const directory = path13.dirname(input.filePath);
|
|
2655
|
-
const fileName = path13.basename(input.filePath);
|
|
2656
|
-
async function readAppended() {
|
|
2657
|
-
const stats = await fs11.stat(input.filePath);
|
|
2658
|
-
if (stats.size < offset) {
|
|
2659
|
-
offset = 0;
|
|
2660
|
-
}
|
|
2661
|
-
if (stats.size === offset) {
|
|
2662
|
-
return;
|
|
2663
|
-
}
|
|
2664
|
-
const handle = await fs11.open(input.filePath, "r");
|
|
2665
|
-
try {
|
|
2666
|
-
const length = stats.size - offset;
|
|
2667
|
-
const buffer = Buffer.alloc(length);
|
|
2668
|
-
await handle.read(buffer, 0, length, offset);
|
|
2669
|
-
offset = stats.size;
|
|
2670
|
-
input.onChunk(buffer.toString("utf8"));
|
|
2671
|
-
} finally {
|
|
2672
|
-
await handle.close();
|
|
2673
|
-
}
|
|
2771
|
+
if (leftNorm === 0 || rightNorm === 0) {
|
|
2772
|
+
return 0;
|
|
2674
2773
|
}
|
|
2675
|
-
|
|
2676
|
-
if (eventType !== "change" || changedFileName?.toString() !== fileName) {
|
|
2677
|
-
return;
|
|
2678
|
-
}
|
|
2679
|
-
void readAppended().catch((error) => {
|
|
2680
|
-
input.onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
2681
|
-
});
|
|
2682
|
-
});
|
|
2683
|
-
return () => watcher.close();
|
|
2774
|
+
return dot / (Math.sqrt(leftNorm) * Math.sqrt(rightNorm));
|
|
2684
2775
|
}
|
|
2685
2776
|
|
|
2686
2777
|
// src/rag/indexer.ts
|
|
@@ -2729,6 +2820,9 @@ function toEvidenceSource2(chunk) {
|
|
|
2729
2820
|
};
|
|
2730
2821
|
}
|
|
2731
2822
|
|
|
2823
|
+
// src/index.ts
|
|
2824
|
+
init_lancedb_store();
|
|
2825
|
+
|
|
2732
2826
|
// src/rag/manual-index.ts
|
|
2733
2827
|
async function processMessagesNow(input) {
|
|
2734
2828
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -2742,7 +2836,8 @@ async function processMessagesNow(input) {
|
|
|
2742
2836
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2743
2837
|
};
|
|
2744
2838
|
}
|
|
2745
|
-
const
|
|
2839
|
+
const { LanceDbVectorStore: LanceDbVectorStore2 } = await Promise.resolve().then(() => (init_lancedb_store(), lancedb_store_exports));
|
|
2840
|
+
const vectorStore = await LanceDbVectorStore2.connectFromConfig(input.config);
|
|
2746
2841
|
try {
|
|
2747
2842
|
const stats = await indexMessageChunks({
|
|
2748
2843
|
messages: new MessageRepository(input.database),
|
|
@@ -3179,7 +3274,7 @@ function createWebApp(config) {
|
|
|
3179
3274
|
note: "\u95EE\u7B54\u5FC5\u987B\u5148\u68C0\u7D22\u8BC1\u636E\uFF0C\u7981\u6B62\u5168\u91CF\u4E0A\u4E0B\u6587\u5806\u53E0\u3002",
|
|
3180
3275
|
retrieval: {
|
|
3181
3276
|
keyword: "SQLite FTS5",
|
|
3182
|
-
vector: "
|
|
3277
|
+
vector: "LanceDB",
|
|
3183
3278
|
hybrid: true
|
|
3184
3279
|
}
|
|
3185
3280
|
},
|
|
@@ -3243,12 +3338,12 @@ export {
|
|
|
3243
3338
|
FileJobRepository,
|
|
3244
3339
|
GatewayIngestor,
|
|
3245
3340
|
HybridRetriever,
|
|
3341
|
+
LanceDbVectorStore,
|
|
3246
3342
|
MemoryVectorStore,
|
|
3247
3343
|
MessageFtsRetriever,
|
|
3248
3344
|
MessageRepository,
|
|
3249
3345
|
OpenAICompatibleChatModel,
|
|
3250
3346
|
OpenAICompatibleEmbeddingModel,
|
|
3251
|
-
SQLiteVectorStore,
|
|
3252
3347
|
VectorRetriever,
|
|
3253
3348
|
appConfigSchema,
|
|
3254
3349
|
appSecretsSchema,
|
|
@@ -3277,8 +3372,10 @@ export {
|
|
|
3277
3372
|
generateGroundedAnswer,
|
|
3278
3373
|
getDatabasePath,
|
|
3279
3374
|
getFeishuQuestionDecision,
|
|
3375
|
+
getGatewayLogPath,
|
|
3280
3376
|
getGatewayPidPath,
|
|
3281
3377
|
getGatewayRuntimeState,
|
|
3378
|
+
getLanceDbPath,
|
|
3282
3379
|
getLogsDirectory,
|
|
3283
3380
|
hasEmbeddingConfig,
|
|
3284
3381
|
indexMessageChunks,
|