@scotthuang/engram 0.5.9 → 0.6.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.
Files changed (47) hide show
  1. package/README.md +42 -0
  2. package/dist/face/src/face-api.d.ts +36 -0
  3. package/dist/face/src/face-api.js +90 -0
  4. package/dist/face/src/face-api.js.map +1 -0
  5. package/dist/face/src/face-store.d.ts +101 -0
  6. package/dist/face/src/face-store.js +361 -0
  7. package/dist/face/src/face-store.js.map +1 -0
  8. package/dist/src/__tests__/bm25.test.d.ts +1 -0
  9. package/dist/src/__tests__/bm25.test.js +198 -0
  10. package/dist/src/__tests__/bm25.test.js.map +1 -0
  11. package/dist/src/__tests__/config.test.d.ts +1 -0
  12. package/dist/src/__tests__/config.test.js +31 -0
  13. package/dist/src/__tests__/config.test.js.map +1 -0
  14. package/dist/src/__tests__/profile.test.d.ts +1 -0
  15. package/dist/src/__tests__/profile.test.js +130 -0
  16. package/dist/src/__tests__/profile.test.js.map +1 -0
  17. package/dist/src/__tests__/recall.test.d.ts +1 -0
  18. package/dist/src/__tests__/recall.test.js +162 -0
  19. package/dist/src/__tests__/recall.test.js.map +1 -0
  20. package/dist/src/bm25.d.ts +60 -0
  21. package/dist/src/bm25.js +271 -0
  22. package/dist/src/bm25.js.map +1 -0
  23. package/dist/src/config.d.ts +47 -0
  24. package/dist/src/config.js +83 -0
  25. package/dist/src/config.js.map +1 -0
  26. package/dist/src/image-store.d.ts +146 -0
  27. package/dist/src/image-store.js +418 -0
  28. package/dist/src/image-store.js.map +1 -0
  29. package/dist/src/index.d.ts +7 -0
  30. package/dist/src/index.js +1236 -0
  31. package/dist/src/index.js.map +1 -0
  32. package/dist/src/logger.d.ts +32 -0
  33. package/dist/src/logger.js +106 -0
  34. package/dist/src/logger.js.map +1 -0
  35. package/dist/src/profile.d.ts +37 -0
  36. package/dist/src/profile.js +107 -0
  37. package/dist/src/profile.js.map +1 -0
  38. package/dist/src/recall.d.ts +98 -0
  39. package/dist/src/recall.js +729 -0
  40. package/dist/src/recall.js.map +1 -0
  41. package/dist/src/settle.d.ts +83 -0
  42. package/dist/src/settle.js +687 -0
  43. package/dist/src/settle.js.map +1 -0
  44. package/dist/src/vector.d.ts +67 -0
  45. package/dist/src/vector.js +284 -0
  46. package/dist/src/vector.js.map +1 -0
  47. package/package.json +3 -3
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Memory System Plugin - BM25 Short-term Memory Index
3
+ *
4
+ * 短期记忆的 BM25 索引构建与搜索
5
+ * 纯 TypeScript 实现,不依赖外部 BM25 库
6
+ */
7
+ import { promises as fs } from "node:fs";
8
+ import { logger } from "./logger.js";
9
+ import { join } from "node:path";
10
+ /**
11
+ * 获取本地日期字符串 (YYYY-MM-DD)
12
+ */
13
+ function getLocalDateString() {
14
+ const now = new Date();
15
+ const year = now.getFullYear();
16
+ const month = String(now.getMonth() + 1).padStart(2, "0");
17
+ const day = String(now.getDate()).padStart(2, "0");
18
+ return `${year}-${month}-${day}`;
19
+ }
20
+ let jiebaInstance = null;
21
+ // 轻量停用词:优先过滤中文口语噪声、助词和标点
22
+ const STOPWORDS = new Set([
23
+ "的", "地", "得", "了", "着", "过",
24
+ "我", "你", "他", "她", "它", "我们", "你们", "他们",
25
+ "这", "那", "这个", "那个", "这里", "那里",
26
+ "是", "有", "在", "和", "与", "及", "并", "或", "而", "但",
27
+ "不", "没", "没有", "很", "也", "都", "就", "还", "又",
28
+ "吗", "呢", "啊", "呀", "吧", "哦", "嗯",
29
+ "什么", "怎么", "为何", "为什么", "怎样", "是否",
30
+ "", ",", "。", "!", "?", "、", ":", ";", "(", ")", "《", "》",
31
+ ",", ".", "!", "?", ":", ";", "(", ")",
32
+ ]);
33
+ function isMeaningfulToken(token) {
34
+ const t = token.trim().toLowerCase();
35
+ if (!t)
36
+ return false;
37
+ if (STOPWORDS.has(t))
38
+ return false;
39
+ // 过滤纯标点 / 符号
40
+ if (/^[\p{P}\p{S}]+$/u.test(t))
41
+ return false;
42
+ return true;
43
+ }
44
+ export async function initJieba() {
45
+ if (!jiebaInstance) {
46
+ const mod = await import("jieba-wasm");
47
+ jiebaInstance = await mod.default;
48
+ }
49
+ }
50
+ export function tokenize(text) {
51
+ if (jiebaInstance) {
52
+ const tokens = jiebaInstance.cut(text);
53
+ return tokens.map((t) => t.trim()).filter(isMeaningfulToken);
54
+ }
55
+ // fallback: 英文按空格分
56
+ return text
57
+ .toLowerCase()
58
+ .replace(/[^\w\u4e00-\u9fff]/g, " ")
59
+ .split(/\s+/)
60
+ .map((t) => t.trim())
61
+ .filter(isMeaningfulToken);
62
+ }
63
+ export class BM25Index {
64
+ docs = [];
65
+ idf = new Map();
66
+ avgDl = 0;
67
+ /**
68
+ * 从短期记忆文件构建索引
69
+ * v0.5: 全量索引所有 .md 文件(不再按 maxAgeDays 截断)
70
+ * maxAgeDays 参数保留但仅用于向下兼容(不再生效)
71
+ */
72
+ async buildFromDirectory(dir, _maxAgeDays = 7) {
73
+ await initJieba();
74
+ this.docs = [];
75
+ // 设计初心:只读取 memory/short-term/
76
+ const shortTermDir = join(dir, "short-term");
77
+ try {
78
+ const files = await fs.readdir(shortTermDir);
79
+ let mdFiles = 0;
80
+ for (const file of files.sort().reverse()) { // 最新的优先
81
+ if (!file.endsWith(".md"))
82
+ continue;
83
+ const filePath = join(shortTermDir, file);
84
+ const content = await fs.readFile(filePath, "utf-8");
85
+ const entries = this.parseEntries(content, filePath, file);
86
+ this.docs.push(...entries);
87
+ mdFiles++;
88
+ }
89
+ logger.info(`[engram:bm25] Scanned ${mdFiles} MD files (full scan, no expiry cutoff)`);
90
+ }
91
+ catch {
92
+ logger.info(`[engram:bm25] short-term directory not found at ${shortTermDir}, 0 entries`);
93
+ }
94
+ logger.info(`[engram:bm25] Built index: ${this.docs.length} entries from ${shortTermDir}`);
95
+ this.buildIDF();
96
+ }
97
+ /**
98
+ * 解析结构化的短期记忆文件
99
+ * 格式:### HH:MM [分类标签]\n摘要内容
100
+ *
101
+ * 特殊处理 [对话记录]: 按 Scott / Shadow 切分每一轮对话
102
+ * 支持两种格式:
103
+ * - 旧格式: Scott: / Shadow:
104
+ * - 新格式: Scott [MM-DD HH:MM]: / Shadow [MM-DD HH:MM]:
105
+ * 每一轮单独做一个 BM25 文档,精准召回不浪费
106
+ */
107
+ parseEntries(content, filePath, fileName) {
108
+ const entries = [];
109
+ const lines = content.split("\n");
110
+ const dateMatch = fileName.match(/(\d{4}-\d{2}-\d{2})/);
111
+ const date = dateMatch ? dateMatch[1] : getLocalDateString();
112
+ let currentCategory = "随聊";
113
+ let currentText = "";
114
+ // 正则匹配 Scott 或 Shadow 开头,可选带时间戳
115
+ // 匹配: "Scott:" / "Shadow:" / "Scott [03-13 12:34]:" / "Shadow [03-13 12:34]:"
116
+ const speakerSplitRegex = /(?=^(?:Scott|Shadow)(?:\s*\[[^\]]+\])?:)/m;
117
+ // 精简格式检测:主题词 | 关键实体 | 摘要
118
+ const condensedLineRegex = /^.+\s*\|\s*.+\s*\|\s*.+$/;
119
+ for (const line of lines) {
120
+ const headerMatch = line.match(/^###\s+\d{2}:\d{2}\s+\[([^\]]+)\]/);
121
+ if (headerMatch) {
122
+ // 保存上一个分类块
123
+ if (currentText.trim()) {
124
+ this.processTextBlock(entries, currentText, currentCategory, date, filePath, speakerSplitRegex, condensedLineRegex);
125
+ }
126
+ currentCategory = headerMatch[1];
127
+ currentText = "";
128
+ }
129
+ else if (line.startsWith("#") || line.startsWith("---") || line.startsWith("```")) {
130
+ continue;
131
+ }
132
+ else {
133
+ currentText += line + "\n";
134
+ }
135
+ }
136
+ // 最后一个分类块
137
+ if (currentText.trim()) {
138
+ this.processTextBlock(entries, currentText, currentCategory, date, filePath, speakerSplitRegex, condensedLineRegex);
139
+ }
140
+ return entries;
141
+ }
142
+ /**
143
+ * 处理文本块,支持三种格式:
144
+ * 1. 精简格式(每行 | 分隔)
145
+ * 2. 对话记录(Scott/Shadow 切分)
146
+ * 3. 普通文本(整块索引)
147
+ */
148
+ processTextBlock(entries, text, category, date, filePath, speakerSplitRegex, condensedLineRegex) {
149
+ const trimmed = text.trim();
150
+ if (!trimmed)
151
+ return;
152
+ const lines = trimmed.split("\n").filter(l => l.trim());
153
+ // 检测是否为精简格式(超过一半的行匹配 | 分隔格式)
154
+ const condensedLines = lines.filter(l => condensedLineRegex.test(l));
155
+ const isCondensedFormat = condensedLines.length > lines.length * 0.5;
156
+ if (isCondensedFormat) {
157
+ // 精简格式:每行单独作为一个文档,关键词密度高
158
+ for (const line of condensedLines) {
159
+ if (line.length < 10)
160
+ continue;
161
+ // 将 | 分隔的内容合并,所有关键词都参与索引
162
+ const parts = line.split("|").map(p => p.trim());
163
+ const fullText = parts.join(" "); // 合并所有部分用于分词
164
+ const entry = {
165
+ text: line, // 原始格式保留,用于展示
166
+ date,
167
+ category: "精简记录",
168
+ filePath,
169
+ };
170
+ entries.push({ tokens: tokenize(fullText), entry });
171
+ }
172
+ }
173
+ else if (category === "对话记录" || category === "对话") {
174
+ // 对话记录分类:按 speaker 切分每一轮
175
+ const turns = trimmed.split(speakerSplitRegex);
176
+ for (const turn of turns) {
177
+ const t = turn.trim();
178
+ if (!t || t.length < 10)
179
+ continue;
180
+ const entry = {
181
+ text: t,
182
+ date,
183
+ category,
184
+ filePath,
185
+ };
186
+ entries.push({ tokens: tokenize(entry.text), entry });
187
+ }
188
+ }
189
+ else {
190
+ // 普通分类:整个块作为一个文档
191
+ const entry = {
192
+ text: trimmed,
193
+ date,
194
+ category,
195
+ filePath,
196
+ };
197
+ entries.push({ tokens: tokenize(entry.text), entry });
198
+ }
199
+ }
200
+ /**
201
+ * 构建 IDF(逆文档频率)
202
+ */
203
+ buildIDF() {
204
+ const df = new Map(); // 文档频率
205
+ let totalLen = 0;
206
+ for (const doc of this.docs) {
207
+ const unique = new Set(doc.tokens);
208
+ for (const t of unique) {
209
+ df.set(t, (df.get(t) || 0) + 1);
210
+ }
211
+ totalLen += doc.tokens.length;
212
+ }
213
+ this.avgDl = this.docs.length > 0 ? totalLen / this.docs.length : 1;
214
+ const N = this.docs.length;
215
+ for (const [term, freq] of df) {
216
+ // IDF = log((N - df + 0.5) / (df + 0.5) + 1)
217
+ this.idf.set(term, Math.log((N - freq + 0.5) / (freq + 0.5) + 1));
218
+ }
219
+ logger.info(`[engram:bm25] buildIDF: ${N} docs, ${df.size} unique terms, avgDl=${this.avgDl.toFixed(1)}`);
220
+ }
221
+ /**
222
+ * BM25 搜索
223
+ * @returns 带分数的结果列表
224
+ */
225
+ async search(query, topK = 3) {
226
+ await initJieba();
227
+ const queryTokens = tokenize(query);
228
+ const k1 = 1.5; // 词频饱和参数
229
+ const b = 0.75; // 文档长度归一化参数
230
+ logger.info(`[engram:bm25] search: query="${query.slice(0, 80)}" tokens=[${queryTokens.join(", ")}] topK=${topK} docs=${this.docs.length} avgDl=${this.avgDl.toFixed(1)}`);
231
+ const scores = [];
232
+ for (const doc of this.docs) {
233
+ let score = 0;
234
+ for (const token of queryTokens) {
235
+ const idf = this.idf.get(token) || 0;
236
+ if (idf === 0)
237
+ continue;
238
+ // 词频
239
+ const tf = doc.tokens.filter(t => t === token).length;
240
+ const dl = doc.tokens.length;
241
+ // BM25 分数
242
+ const tfComponent = (tf * (k1 + 1)) / (tf + k1 * (1 - b + b * (dl / this.avgDl)));
243
+ score += idf * tfComponent;
244
+ }
245
+ if (score > 0) {
246
+ scores.push({ entry: doc.entry, score });
247
+ }
248
+ }
249
+ scores.sort((a, b) => b.score - a.score);
250
+ const result = scores.slice(0, topK);
251
+ logger.info(`[engram:bm25] search: ${scores.length} docs matched, returning top ${result.length}`);
252
+ return result;
253
+ }
254
+ /**
255
+ * 添加单条记录(用于实时写入后的即时索引)
256
+ */
257
+ async addEntry(entry) {
258
+ await initJieba();
259
+ const tokens = tokenize(entry.text);
260
+ this.docs.push({
261
+ tokens,
262
+ entry,
263
+ });
264
+ logger.info(`[engram:bm25] addEntry: "${entry.text.slice(0, 60)}" → ${tokens.length} tokens, total docs=${this.docs.length}`);
265
+ this.buildIDF();
266
+ }
267
+ get size() {
268
+ return this.docs.length;
269
+ }
270
+ }
271
+ //# sourceMappingURL=bm25.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bm25.js","sourceRoot":"","sources":["../../src/bm25.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;GAEG;AACH,SAAS,kBAAkB;IACzB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAC/B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC1D,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACnD,OAAO,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG,EAAE,CAAC;AACnC,CAAC;AAcD,IAAI,aAAa,GAAQ,IAAI,CAAC;AAE9B,yBAAyB;AACzB,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC;IACxB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG;IAC5B,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;IACzC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;IAChC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG;IAChD,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG;IAC5C,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG;IACjC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI;IACnC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG;IACzD,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG;CACvC,CAAC,CAAC;AAEH,SAAS,iBAAiB,CAAC,KAAa;IACtC,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrB,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACnC,aAAa;IACb,IAAI,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QACvC,aAAa,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC;IACpC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,CAAa,CAAC;QACnD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAC/D,CAAC;IACD,mBAAmB;IACnB,OAAO,IAAI;SACR,WAAW,EAAE;SACb,OAAO,CAAC,qBAAqB,EAAE,GAAG,CAAC;SACnC,KAAK,CAAC,KAAK,CAAC;SACZ,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,iBAAiB,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,OAAO,SAAS;IACZ,IAAI,GAAc,EAAE,CAAC;IACrB,GAAG,GAAwB,IAAI,GAAG,EAAE,CAAC;IACrC,KAAK,GAAG,CAAC,CAAC;IAElB;;;;OAIG;IACH,KAAK,CAAC,kBAAkB,CAAC,GAAW,EAAE,cAAsB,CAAC;QAC3D,MAAM,SAAS,EAAE,CAAC;QAClB,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;QAEf,8BAA8B;QAC9B,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;QAE7C,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;YAC7C,IAAI,OAAO,GAAG,CAAC,CAAC;YAChB,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,QAAQ;gBACnD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;oBAAE,SAAS;gBACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;gBAE1C,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBACrD,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;gBAC3D,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;gBAC3B,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,yBAAyB,OAAO,yCAAyC,CAAC,CAAC;QACzF,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,CAAC,IAAI,CAAC,mDAAmD,YAAY,aAAa,CAAC,CAAC;QAC5F,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,8BAA8B,IAAI,CAAC,IAAI,CAAC,MAAM,iBAAiB,YAAY,EAAE,CAAC,CAAC;QAC3F,IAAI,CAAC,QAAQ,EAAE,CAAC;IAClB,CAAC;IAED;;;;;;;;;OASG;IACK,YAAY,CAAC,OAAe,EAAE,QAAgB,EAAE,QAAgB;QACtE,MAAM,OAAO,GAAc,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;QACxD,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB,EAAE,CAAC;QAE7D,IAAI,eAAe,GAAG,IAAI,CAAC;QAC3B,IAAI,WAAW,GAAG,EAAE,CAAC;QAErB,gCAAgC;QAChC,8EAA8E;QAC9E,MAAM,iBAAiB,GAAG,2CAA2C,CAAC;QAEtE,yBAAyB;QACzB,MAAM,kBAAkB,GAAG,0BAA0B,CAAC;QAEtD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;YACpE,IAAI,WAAW,EAAE,CAAC;gBAChB,WAAW;gBACX,IAAI,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC;oBACvB,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,iBAAiB,EAAE,kBAAkB,CAAC,CAAC;gBACtH,CAAC;gBACD,eAAe,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;gBACjC,WAAW,GAAG,EAAE,CAAC;YACnB,CAAC;iBAAM,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;gBACpF,SAAS;YACX,CAAC;iBAAM,CAAC;gBACN,WAAW,IAAI,IAAI,GAAG,IAAI,CAAC;YAC7B,CAAC;QACH,CAAC;QAED,UAAU;QACV,IAAI,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC;YACvB,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,iBAAiB,EAAE,kBAAkB,CAAC,CAAC;QACtH,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;;OAKG;IACK,gBAAgB,CACtB,OAAkB,EAClB,IAAY,EACZ,QAAgB,EAChB,IAAY,EACZ,QAAgB,EAChB,iBAAyB,EACzB,kBAA0B;QAE1B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAExD,6BAA6B;QAC7B,MAAM,cAAc,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACrE,MAAM,iBAAiB,GAAG,cAAc,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC;QAErE,IAAI,iBAAiB,EAAE,CAAC;YACtB,yBAAyB;YACzB,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;gBAClC,IAAI,IAAI,CAAC,MAAM,GAAG,EAAE;oBAAE,SAAS;gBAC/B,yBAAyB;gBACzB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;gBACjD,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,aAAa;gBAC/C,MAAM,KAAK,GAAmB;oBAC5B,IAAI,EAAE,IAAI,EAAE,cAAc;oBAC1B,IAAI;oBACJ,QAAQ,EAAE,MAAM;oBAChB,QAAQ;iBACT,CAAC;gBACF,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;YACtD,CAAC;QACH,CAAC;aAAM,IAAI,QAAQ,KAAK,MAAM,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACpD,yBAAyB;YACzB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;YAC/C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;gBACtB,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,EAAE;oBAAE,SAAS;gBAClC,MAAM,KAAK,GAAmB;oBAC5B,IAAI,EAAE,CAAC;oBACP,IAAI;oBACJ,QAAQ;oBACR,QAAQ;iBACT,CAAC;gBACF,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;YACxD,CAAC;QACH,CAAC;aAAM,CAAC;YACN,iBAAiB;YACjB,MAAM,KAAK,GAAmB;gBAC5B,IAAI,EAAE,OAAO;gBACb,IAAI;gBACJ,QAAQ;gBACR,QAAQ;aACT,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IAED;;OAEG;IACK,QAAQ;QACd,MAAM,EAAE,GAAwB,IAAI,GAAG,EAAE,CAAC,CAAC,OAAO;QAClD,IAAI,QAAQ,GAAG,CAAC,CAAC;QAEjB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACnC,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;gBACvB,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAClC,CAAC;YACD,QAAQ,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC;QAChC,CAAC;QAED,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAEpE,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QAC3B,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YAC9B,6CAA6C;YAC7C,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACpE,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,2BAA2B,CAAC,UAAU,EAAE,CAAC,IAAI,wBAAwB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC5G,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,OAAe,CAAC;QAC1C,MAAM,SAAS,EAAE,CAAC;QAClB,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QACpC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,SAAS;QACzB,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,YAAY;QAE5B,MAAM,CAAC,IAAI,CAAC,gCAAgC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,SAAS,IAAI,CAAC,IAAI,CAAC,MAAM,UAAU,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAE3K,MAAM,MAAM,GAAoD,EAAE,CAAC;QAEnE,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,KAAK,GAAG,CAAC,CAAC;YACd,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;gBAChC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACrC,IAAI,GAAG,KAAK,CAAC;oBAAE,SAAS;gBAExB,KAAK;gBACL,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,MAAM,CAAC;gBACtD,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC;gBAE7B,UAAU;gBACV,MAAM,WAAW,GAAG,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAClF,KAAK,IAAI,GAAG,GAAG,WAAW,CAAC;YAC7B,CAAC;YAED,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;gBACd,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QACrC,MAAM,CAAC,IAAI,CAAC,yBAAyB,MAAM,CAAC,MAAM,gCAAgC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACnG,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,QAAQ,CAAC,KAAqB;QAClC,MAAM,SAAS,EAAE,CAAC;QAClB,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;YACb,MAAM;YACN,KAAK;SACN,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,4BAA4B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,MAAM,CAAC,MAAM,uBAAuB,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QAC9H,IAAI,CAAC,QAAQ,EAAE,CAAC;IAClB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;IAC1B,CAAC;CACF"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Memory System Plugin - Configuration
3
+ */
4
+ export type MemorySystemConfig = {
5
+ shortTermDays: number;
6
+ halfLifeDays: number;
7
+ recallTopK: number;
8
+ minScore: number;
9
+ vectorWeight: number;
10
+ textWeight: number;
11
+ settleModel: string;
12
+ embeddingModel: string;
13
+ /** Embedding API 配置 */
14
+ embedding: {
15
+ model: string;
16
+ apiKey: string;
17
+ baseUrl: string;
18
+ dimensions: number;
19
+ };
20
+ /** 精简 LLM API 配置(Anthropic Messages 格式)*/
21
+ condense: {
22
+ apiKey: string;
23
+ baseUrl: string;
24
+ model: string;
25
+ };
26
+ /** 查询重写配置 */
27
+ queryRewrite: {
28
+ /** 是否启用查询重写,默认 false */
29
+ enabled: boolean;
30
+ /** LLM API Key(留空则 fallback 到 condense.apiKey) */
31
+ apiKey?: string;
32
+ /** LLM API Base URL(留空则 fallback 到 condense.baseUrl) */
33
+ baseUrl?: string;
34
+ /** LLM 模型名(留空则 fallback 到 condense.model) */
35
+ model?: string;
36
+ /** LLM 调用超时(毫秒),默认 10000 */
37
+ timeoutMs: number;
38
+ };
39
+ /** 日志父目录路径,传入后在此目录下创建 engram/ 子目录按天写日志文件。不传则仅 console 输出 */
40
+ logDir?: string;
41
+ /** LanceDB 路径 */
42
+ lancedbDir?: string;
43
+ /** 是否保存中间文件(staging)供调试,默认 true */
44
+ saveStagingFile?: boolean;
45
+ };
46
+ export declare const DEFAULTS: MemorySystemConfig;
47
+ export declare function parseConfig(raw?: Record<string, unknown>): MemorySystemConfig;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Memory System Plugin - Configuration
3
+ */
4
+ import { logger } from "./logger.js";
5
+ export const DEFAULTS = {
6
+ shortTermDays: 14,
7
+ halfLifeDays: 30,
8
+ recallTopK: 3,
9
+ minScore: 0.01,
10
+ vectorWeight: 0.7,
11
+ textWeight: 0.3,
12
+ settleModel: "MiniMax-M2.7-highspeed",
13
+ embeddingModel: "text-embedding-v3",
14
+ embedding: {
15
+ model: "text-embedding-v3",
16
+ apiKey: "",
17
+ baseUrl: "",
18
+ dimensions: 1024,
19
+ },
20
+ condense: {
21
+ apiKey: "",
22
+ baseUrl: "https://api.minimaxi.com/anthropic",
23
+ model: "MiniMax-M2.7-highspeed",
24
+ },
25
+ queryRewrite: {
26
+ enabled: false,
27
+ timeoutMs: 10000,
28
+ },
29
+ };
30
+ export function parseConfig(raw) {
31
+ if (!raw) {
32
+ logger.info(`[engram:config] No raw config provided, using defaults`);
33
+ return { ...DEFAULTS };
34
+ }
35
+ logger.info(`[engram:config] Parsing config, keys=[${Object.keys(raw).join(", ")}]`);
36
+ const embedding = typeof raw.embedding === "object" && raw.embedding !== null
37
+ ? {
38
+ model: typeof raw.embedding.model === "string" ? raw.embedding.model : DEFAULTS.embedding.model,
39
+ apiKey: typeof raw.embedding.apiKey === "string" ? raw.embedding.apiKey : DEFAULTS.embedding.apiKey,
40
+ baseUrl: typeof raw.embedding.baseUrl === "string" ? raw.embedding.baseUrl : DEFAULTS.embedding.baseUrl,
41
+ dimensions: typeof raw.embedding.dimensions === "number" ? raw.embedding.dimensions : DEFAULTS.embedding.dimensions,
42
+ }
43
+ : DEFAULTS.embedding;
44
+ const condense = typeof raw.condense === "object" && raw.condense !== null
45
+ ? {
46
+ apiKey: typeof raw.condense.apiKey === "string" ? raw.condense.apiKey : DEFAULTS.condense.apiKey,
47
+ baseUrl: typeof raw.condense.baseUrl === "string" ? raw.condense.baseUrl : DEFAULTS.condense.baseUrl,
48
+ model: typeof raw.condense.model === "string" ? raw.condense.model : DEFAULTS.condense.model,
49
+ }
50
+ : DEFAULTS.condense;
51
+ const queryRewrite = typeof raw.queryRewrite === "object" && raw.queryRewrite !== null
52
+ ? {
53
+ enabled: typeof raw.queryRewrite.enabled === "boolean" ? raw.queryRewrite.enabled : DEFAULTS.queryRewrite.enabled,
54
+ apiKey: typeof raw.queryRewrite.apiKey === "string" ? raw.queryRewrite.apiKey : undefined,
55
+ baseUrl: typeof raw.queryRewrite.baseUrl === "string" ? raw.queryRewrite.baseUrl : undefined,
56
+ model: typeof raw.queryRewrite.model === "string" ? raw.queryRewrite.model : undefined,
57
+ timeoutMs: typeof raw.queryRewrite.timeoutMs === "number" ? raw.queryRewrite.timeoutMs : DEFAULTS.queryRewrite.timeoutMs,
58
+ }
59
+ : DEFAULTS.queryRewrite;
60
+ const config = {
61
+ shortTermDays: typeof raw.shortTermDays === "number" ? raw.shortTermDays : DEFAULTS.shortTermDays,
62
+ halfLifeDays: typeof raw.halfLifeDays === "number" ? raw.halfLifeDays : DEFAULTS.halfLifeDays,
63
+ recallTopK: typeof raw.recallTopK === "number" ? raw.recallTopK : DEFAULTS.recallTopK,
64
+ minScore: typeof raw.minScore === "number" ? raw.minScore : DEFAULTS.minScore,
65
+ vectorWeight: typeof raw.vectorWeight === "number" ? raw.vectorWeight : DEFAULTS.vectorWeight,
66
+ textWeight: typeof raw.textWeight === "number" ? raw.textWeight : DEFAULTS.textWeight,
67
+ settleModel: typeof raw.settleModel === "string" ? raw.settleModel : DEFAULTS.settleModel,
68
+ embeddingModel: typeof raw.embeddingModel === "string" ? raw.embeddingModel : DEFAULTS.embeddingModel,
69
+ embedding,
70
+ condense,
71
+ queryRewrite,
72
+ lancedbDir: typeof raw.lancedbDir === "string" ? raw.lancedbDir : undefined,
73
+ logDir: typeof raw.logDir === "string" ? raw.logDir : undefined,
74
+ saveStagingFile: typeof raw.saveStagingFile === "boolean" ? raw.saveStagingFile : true,
75
+ };
76
+ logger.info(`[engram:config] Parsed: shortTermDays=${config.shortTermDays} halfLifeDays=${config.halfLifeDays} recallTopK=${config.recallTopK} minScore=${config.minScore} vectorWeight=${config.vectorWeight} textWeight=${config.textWeight}`);
77
+ logger.info(`[engram:config] Embedding: model=${config.embedding.model} dims=${config.embedding.dimensions} hasApiKey=${!!config.embedding.apiKey} baseUrl=${config.embedding.baseUrl.slice(0, 40)}`);
78
+ logger.info(`[engram:config] Condense: model=${config.condense.model} hasApiKey=${!!config.condense.apiKey}`);
79
+ logger.info(`[engram:config] QueryRewrite: enabled=${config.queryRewrite.enabled} timeoutMs=${config.queryRewrite.timeoutMs}`);
80
+ logger.info(`[engram:config] LogDir: ${config.logDir || "(none, console only)"}`);
81
+ return config;
82
+ }
83
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Image Store - 图片本地持久化存储
3
+ *
4
+ * 将图片从临时路径(如微信下发的 /tmp 目录)copy 到持久化目录,
5
+ * 按日期归档,配套生成元数据 JSON 文件,支持 SHA-256 去重。
6
+ *
7
+ * 存储结构:
8
+ * memory-engram/images/YYYY-MM/DD/
9
+ * ├── YYYYMMDD_HHmmss_{hash6}.jpg ← 原图
10
+ * └── YYYYMMDD_HHmmss_{hash6}.json ← 元数据
11
+ */
12
+ export interface ImageMetadata {
13
+ /** 原始文件路径 */
14
+ originalPath: string;
15
+ /** 持久化后的绝对路径 */
16
+ storedPath: string;
17
+ /** 相对于 memory-engram/ 的路径(用于记忆引用) */
18
+ relativePath: string;
19
+ /** 存储时间戳(毫秒) */
20
+ timestamp: number;
21
+ /** 来源渠道 */
22
+ source: string;
23
+ /** 图片文件 SHA-256 hash */
24
+ imageHash: string;
25
+ /** 文件大小(字节) */
26
+ fileSize: number;
27
+ /** 图片描述(由 3.1.2 图片理解填写) */
28
+ description: string;
29
+ /** 识别到的实体(由 3.1.2 图片理解填写) */
30
+ entities: string[];
31
+ /** 人脸识别结果(由 3.2 人脸识别填写) */
32
+ faces: Array<{
33
+ personId: string | null;
34
+ name: string;
35
+ confidence: number;
36
+ bbox: number[];
37
+ }>;
38
+ }
39
+ export interface ImageStoreResult {
40
+ /** 持久化后的绝对路径 */
41
+ storedPath: string;
42
+ /** 相对于 memory-engram/ 的路径(用于记忆引用) */
43
+ relativePath: string;
44
+ /** 完整元数据 */
45
+ metadata: ImageMetadata;
46
+ /** 是否为重复图片(已存在相同 hash) */
47
+ isDuplicate: boolean;
48
+ }
49
+ /**
50
+ * 图片持久化存储管理器
51
+ */
52
+ export declare class ImageStore {
53
+ private workspaceDir;
54
+ /** hash → relativePath 的去重索引 */
55
+ private hashIndex;
56
+ constructor(workspaceDir: string);
57
+ /** memory-engram 根目录 */
58
+ private get engramDir();
59
+ /** images 根目录 */
60
+ private get imagesDir();
61
+ /** hash 索引文件绝对路径 */
62
+ private get hashIndexPath();
63
+ /**
64
+ * 计算文件的 SHA-256 hash
65
+ */
66
+ private computeFileHash;
67
+ /**
68
+ * 加载 hash 去重索引
69
+ */
70
+ private loadHashIndex;
71
+ /**
72
+ * 保存 hash 去重索引
73
+ */
74
+ private saveHashIndex;
75
+ /**
76
+ * 生成本地时间相关的路径组件
77
+ */
78
+ private getTimeParts;
79
+ /**
80
+ * 存储图片到持久化目录
81
+ *
82
+ * @param originalPath 图片的原始路径(如 /tmp/wechat_xxx.jpg)
83
+ * @param options.source 来源渠道(默认 "unknown")
84
+ * @returns 存储结果,包含新路径和元数据
85
+ * @throws 如果文件不存在或扩展名不支持
86
+ */
87
+ storeImage(originalPath: string, options?: {
88
+ source?: string;
89
+ }): Promise<ImageStoreResult>;
90
+ /**
91
+ * 更新图片元数据(用于 3.1.2 填写 description/entities,3.2 填写 faces)
92
+ *
93
+ * @param relativePath 相对于 memory-engram/ 的图片路径
94
+ * @param updates 要更新的字段
95
+ */
96
+ updateMetadata(relativePath: string, updates: Partial<Pick<ImageMetadata, "description" | "entities" | "faces">>): Promise<ImageMetadata | null>;
97
+ /**
98
+ * 根据 hash 查找已存储的图片
99
+ */
100
+ findByHash(imageHash: string): Promise<ImageMetadata | null>;
101
+ /**
102
+ * 列出某天的所有图片
103
+ *
104
+ * @param date 日期字符串 YYYY-MM-DD
105
+ */
106
+ listByDate(date: string): Promise<ImageMetadata[]>;
107
+ /**
108
+ * 获取图片存储统计
109
+ */
110
+ getStats(): Promise<{
111
+ totalImages: number;
112
+ totalSizeBytes: number;
113
+ }>;
114
+ /**
115
+ * 构建元数据对象
116
+ */
117
+ private buildMetadata;
118
+ /**
119
+ * 遍历 images/ 目录,加载所有图片元数据 JSON
120
+ */
121
+ private loadAllMetadata;
122
+ /**
123
+ * 文本检索图片:jieba 分词 + BM25 打分
124
+ *
125
+ * @param query 搜索关键词(如 "微信合照")
126
+ * @param limit 返回数量上限
127
+ * @returns 按相关性排序的图片列表
128
+ */
129
+ searchByText(query: string, limit?: number): Promise<ImageSearchResult[]>;
130
+ }
131
+ export interface ImageSearchResult {
132
+ /** 相对于 memory-engram/ 的路径 */
133
+ relativePath: string;
134
+ /** 绝对路径 */
135
+ storedPath: string;
136
+ /** 存储日期 YYYY-MM-DD */
137
+ date: string;
138
+ /** 描述摘要(前 200 字) */
139
+ description: string;
140
+ /** 实体关键词 */
141
+ entities: string[];
142
+ /** 来源渠道 */
143
+ source: string;
144
+ /** BM25 相关性分数 */
145
+ score: number;
146
+ }