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/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
- import fs5 from "fs/promises";
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
- import fs4 from "fs";
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 path7.join(getChatterCatcherHome(), "gateway.pid");
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 = fs4.readFileSync(pidFile, "utf8");
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
- fs4.mkdirSync(path7.dirname(pidFile), { recursive: true });
552
- fs4.writeFileSync(pidFile, `${JSON.stringify(record, null, 2)}
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
- fs4.rmSync(pidFile, { force: true });
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 vectorStore = new SQLiteVectorStore(input.messages.database);
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 checkSqliteVector(config));
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 fs5.mkdir(home, { recursive: true });
1280
- await fs5.access(home);
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 SQLite Vector \u8BED\u4E49\u68C0\u7D22\u3002");
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 checkSqliteVector(config) {
1336
- let database = null;
1505
+ async function checkLanceDb(config) {
1506
+ let store = null;
1337
1507
  try {
1338
- database = openDatabase(config);
1339
- const store = new SQLiteVectorStore(database);
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("SQLite Vector", `${getDatabasePath(config)}\uFF1Bvectors=${count}`);
1511
+ return pass("LanceDB", `${getLanceDbPath2(config)}\uFF1Bvectors=${count}`);
1342
1512
  } catch (error) {
1343
- return fail("SQLite Vector", error instanceof Error ? error.message : String(error));
1513
+ return fail("LanceDB", error instanceof Error ? error.message : String(error));
1344
1514
  } finally {
1345
- database?.close();
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
- import fs6 from "fs/promises";
1387
- import path8 from "path";
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 path8.join(resolveHomePath(config.storage.dataDir), "exports", fileName);
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 = path8.resolve(input.outputPath ?? defaultExportPath(input.config, exportedAt));
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 fs6.mkdir(path8.dirname(outputPath), { recursive: true });
1498
- await fs6.writeFile(outputPath, `${JSON.stringify(payload, null, 2)}
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 fs7 from "fs/promises";
1511
- import path9 from "path";
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 = path9.resolve(input.inputPath);
1560
- const payload = parsePayload(await fs7.readFile(inputPath, "utf8"));
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 fs8 from "fs/promises";
2304
- import path10 from "path";
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 = path10.join(this.dataDir, "files", "feishu");
2343
- await fs8.mkdir(targetDir, { recursive: true });
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 = path10.join(targetDir, fileName);
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 fs10 from "fs/promises";
2369
- import path12 from "path";
2541
+ import fs12 from "fs/promises";
2542
+ import path14 from "path";
2370
2543
 
2371
2544
  // src/files/parser.ts
2372
- import fs9 from "fs/promises";
2373
- import path11 from "path";
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 = path11.extname(filePath).toLowerCase();
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 = path11.extname(filePath).toLowerCase();
2560
+ const extension = path13.extname(filePath).toLowerCase();
2388
2561
  if (TEXT_EXTENSIONS.has(extension)) {
2389
2562
  return {
2390
- text: await fs9.readFile(filePath, "utf8"),
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 fs9.readFile(filePath);
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 = path12.extname(filePath).toLowerCase();
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 = path12.resolve(input.filePath);
2436
- const fileName = path12.basename(sourcePath);
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 fs10.stat(sourcePath);
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 = path12.join(resolveHomePath(input.config.storage.dataDir), "files");
2450
- await fs10.mkdir(fileDir, { recursive: true });
2451
- const storedPath = path12.join(fileDir, stableStoredName(sourcePath, fileName));
2452
- await fs10.copyFile(sourcePath, storedPath);
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/logs/reader.ts
2584
- import fs11 from "fs/promises";
2585
- import { watch } from "fs";
2586
- import path13 from "path";
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
- const [latest] = await listLogFiles(input.logsDir);
2647
- if (!latest) {
2648
- return null;
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
- return readLogTail({ filePath: latest.path, lines: input.lines });
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
- const watcher = watch(directory, (eventType, changedFileName) => {
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 vectorStore = new SQLiteVectorStore(input.database);
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: "SQLite 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,