dsh-tdai-memory 0.2.10 → 0.2.12

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 (72) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +137 -129
  3. package/README.zh.md +75 -70
  4. package/THIRD-PARTY-NOTICES.md +20 -20
  5. package/client.js +350 -349
  6. package/cordis.patch.yml +8 -8
  7. package/index.js +414 -421
  8. package/package.json +17 -10
  9. package/recall-inject.js +86 -86
  10. package/vendor/tdai/adapters/index.js +15 -15
  11. package/vendor/tdai/adapters/openclaw/host-adapter.js +83 -83
  12. package/vendor/tdai/adapters/openclaw/index.js +5 -5
  13. package/vendor/tdai/adapters/openclaw/llm-runner.js +69 -69
  14. package/vendor/tdai/adapters/standalone/host-adapter.js +58 -58
  15. package/vendor/tdai/adapters/standalone/index.js +5 -5
  16. package/vendor/tdai/adapters/standalone/llm-runner.js +246 -246
  17. package/vendor/tdai/config.js +363 -363
  18. package/vendor/tdai/core/conversation/l0-recorder.js +420 -420
  19. package/vendor/tdai/core/hooks/auto-capture.js +236 -236
  20. package/vendor/tdai/core/hooks/auto-recall.js +640 -640
  21. package/vendor/tdai/core/index.js +8 -8
  22. package/vendor/tdai/core/persona/persona-generator.js +184 -184
  23. package/vendor/tdai/core/persona/persona-trigger.js +94 -94
  24. package/vendor/tdai/core/profile/profile-sync.js +200 -200
  25. package/vendor/tdai/core/prompts/l1-dedup.js +72 -72
  26. package/vendor/tdai/core/prompts/l1-extraction.js +34 -34
  27. package/vendor/tdai/core/prompts/persona-generation.js +35 -35
  28. package/vendor/tdai/core/prompts/scene-extraction.js +45 -45
  29. package/vendor/tdai/core/record/l1-dedup.js +318 -318
  30. package/vendor/tdai/core/record/l1-extractor.js +379 -379
  31. package/vendor/tdai/core/record/l1-reader.js +178 -178
  32. package/vendor/tdai/core/record/l1-writer.js +141 -141
  33. package/vendor/tdai/core/report/reporter.js +86 -86
  34. package/vendor/tdai/core/scene/filename-normalizer.js +161 -161
  35. package/vendor/tdai/core/scene/scene-extractor.js +410 -410
  36. package/vendor/tdai/core/scene/scene-format.js +53 -53
  37. package/vendor/tdai/core/scene/scene-index.js +82 -82
  38. package/vendor/tdai/core/scene/scene-navigation.js +57 -57
  39. package/vendor/tdai/core/seed/input.js +404 -404
  40. package/vendor/tdai/core/seed/seed-runtime.js +314 -314
  41. package/vendor/tdai/core/seed/types.js +9 -9
  42. package/vendor/tdai/core/store/bm25-client.js +117 -117
  43. package/vendor/tdai/core/store/bm25-local.js +68 -68
  44. package/vendor/tdai/core/store/embedding.js +632 -632
  45. package/vendor/tdai/core/store/factory.js +95 -95
  46. package/vendor/tdai/core/store/search-utils.js +54 -54
  47. package/vendor/tdai/core/store/sqlite.js +1632 -1632
  48. package/vendor/tdai/core/store/tcvdb-client.js +221 -221
  49. package/vendor/tdai/core/store/tcvdb.js +1047 -1047
  50. package/vendor/tdai/core/store/types.js +17 -17
  51. package/vendor/tdai/core/tdai-core.js +446 -446
  52. package/vendor/tdai/core/tools/conversation-search.js +199 -199
  53. package/vendor/tdai/core/tools/memory-search.js +207 -207
  54. package/vendor/tdai/core/types.js +12 -12
  55. package/vendor/tdai/utils/backup.js +189 -189
  56. package/vendor/tdai/utils/checkpoint.js +360 -360
  57. package/vendor/tdai/utils/clean-context-runner.js +472 -472
  58. package/vendor/tdai/utils/ensure-hook-policy.js +213 -213
  59. package/vendor/tdai/utils/env.js +13 -13
  60. package/vendor/tdai/utils/managed-timer.js +127 -127
  61. package/vendor/tdai/utils/manifest.js +91 -91
  62. package/vendor/tdai/utils/memory-cleaner.js +300 -300
  63. package/vendor/tdai/utils/no-think-fetch.js +109 -109
  64. package/vendor/tdai/utils/openclaw-state-dir.js +26 -26
  65. package/vendor/tdai/utils/pipeline-factory.js +517 -517
  66. package/vendor/tdai/utils/pipeline-manager.js +892 -892
  67. package/vendor/tdai/utils/sanitize.js +360 -360
  68. package/vendor/tdai/utils/serial-queue.js +103 -103
  69. package/vendor/tdai/utils/session-filter.js +88 -88
  70. package/vendor/tdai/utils/text-utils.js +29 -29
  71. package/vendor/tdai/utils/time.js +252 -252
  72. package/vendor/dsh-settings-expose.js +0 -75
package/client.js CHANGED
@@ -1,349 +1,350 @@
1
- /**
2
- * dsh-tdai-memory — browser half.
3
- *
4
- * A "记忆" section inside the Web UI settings page: edits the `tdai-memory`
5
- * settings namespace (data dir, extraction LLM, embeddings, capture/extract/
6
- * recall switches, tools) through the settings scope transport plus nested
7
- * `settings.mutate` ops. TdaiCore is built at startup, so changes apply
8
- * after a restart (noted in the UI).
9
- *
10
- * Hand-written ModuleLoader bundle — no build step required.
11
- */
12
- window.__ModuleLoader__.load({
13
- id: "dsh-tdai-memory",
14
- factory: (require) => {
15
- var module = { exports: {} };
16
- var exports = module.exports;
17
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
18
- var react = require("react");
19
- var h = react.createElement;
20
-
21
- // ── CSS (theme tokens) ────────────────────────────────────────────────
22
- var CSS = ".__tm_root{max-width:640px;display:flex;flex-direction:column;gap:10px}" +
23
- ".__tm_group{font-size:13px;font-weight:700;color:var(--dsw-alias-label-primary);border-bottom:1px solid var(--dsw-alias-border-l2);padding-bottom:4px;margin:6px 0 2px}" +
24
- ".__tm_field{display:flex;flex-direction:column;gap:4px}" +
25
- ".__tm_label{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;gap:6px}" +
26
- ".__tm_override{font-size:10px;color:var(--dsw-alias-state-business-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:4px;padding:0 4px}" +
27
- ".__tm_hint{font-size:11px;color:var(--dsw-alias-label-tertiary)}" +
28
- ".__tm_input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:6px 10px;font-size:13px;box-sizing:border-box;width:100%}" +
29
- ".__tm_row{display:flex;align-items:center;gap:8px}" +
30
- ".__tm_check{accent-color:var(--dsw-alias-state-business-primary)}" +
31
- ".__tm_actions{display:flex;gap:8px;align-items:center;margin-top:4px}" +
32
- ".__tm_btn{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:8px;padding:6px 14px;font:inherit;font-size:13px;cursor:pointer}" +
33
- ".__tm_btn:hover:not(:disabled){border-color:var(--dsw-alias-state-business-primary)}" +
34
- ".__tm_btn:disabled{opacity:.5;cursor:default}" +
35
- ".__tm_btnPrimary{border-color:var(--dsw-alias-state-business-primary, #3964fe);background:var(--dsw-alias-state-business-primary, #3964fe);color:#fff}" +
36
- ".__tm_status{font-size:12px;color:var(--dsw-alias-label-tertiary)}" +
37
- ".__tm_error{font-size:12px;color:var(--dsw-alias-state-error-primary)}" +
38
- ".__tm_unavailable{font-size:13px;color:var(--dsw-alias-label-tertiary)}";
39
- var tagId = "dsh-tdai-memory/main.css";
40
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
41
- var tag = document.createElement("style");
42
- tag.dataset.plugin = "dsh-tdai-memory";
43
- tag.dataset.pluginCss = tagId;
44
- tag.textContent = CSS;
45
- document.head.appendChild(tag);
46
- }
47
-
48
- // ── locale ────────────────────────────────────────────────────────────
49
- var NS = "tdaiMemory";
50
- var inject = ["slots", "locale", "settingsScope", "connection"];
51
- var zh = {
52
- nav: "记忆",
53
- intro: "TDAI 记忆配置:L0 捕获 → L1 结构化提取 → 召回注入。密钥只写不读。TdaiCore 在启动时构建,修改后需重启生效。",
54
- groupData: "数据",
55
- groupLlm: "提取 LLM(L1/L2/L3)",
56
- groupEmbedding: "向量 Embedding",
57
- groupCapture: "捕获",
58
- groupExtraction: "提取",
59
- groupRecall: "召回",
60
- groupTools: "工具",
61
- fieldDataDir: "数据目录(空 = ~/.memory-tencentdb/memory-tdai)",
62
- fieldLlmBaseUrl: "Base URL",
63
- fieldLlmApiKey: "API Key",
64
- fieldLlmModel: "模型",
65
- fieldLlmMaxTokens: "最大输出 Tokens",
66
- fieldLlmTimeoutMs: "超时(毫秒)",
67
- fieldEmbeddingBaseUrl: "Base URL",
68
- fieldEmbeddingApiKey: "API Key",
69
- fieldEmbeddingModel: "模型",
70
- fieldEmbeddingDimensions: "向量维度",
71
- fieldEmbeddingSendDimensions: "请求带 dimensions 参数",
72
- fieldCaptureEnabled: "捕获对话(L0)",
73
- fieldExtractionEnabled: "结构化提取(L1)",
74
- fieldExtractionEnableDedup: "冲突检测(额外 LLM 调用)",
75
- fieldRecallEnabled: "召回注入",
76
- fieldRecallMaxResults: "最大召回条数",
77
- fieldRecallScoreThreshold: "相似度阈值",
78
- fieldRecallTimeoutMs: "召回超时(毫秒)",
79
- fieldToolsEnabled: "注册搜索工具(tdai_memory_search / tdai_conversation_search)",
80
- secretHint: "留空保持当前密钥。",
81
- save: "保存",
82
- reset: "恢复默认",
83
- saved: "已保存(重启后生效)",
84
- saving: "保存中…",
85
- error: "保存失败",
86
- unavailable: "设置命名空间不可用(服务端未注册 tdai-memory 命名空间?)",
87
- overridden: "已覆盖",
88
- loading: "加载中…"
89
- };
90
- var en = {
91
- nav: "Memory",
92
- intro: "TDAI memory config: L0 capture → L1 extraction → recall injection. Keys are write-only. TdaiCore is built at startup; changes apply after a restart.",
93
- groupData: "Data",
94
- groupLlm: "Extraction LLM (L1/L2/L3)",
95
- groupEmbedding: "Embeddings",
96
- groupCapture: "Capture",
97
- groupExtraction: "Extraction",
98
- groupRecall: "Recall",
99
- groupTools: "Tools",
100
- fieldDataDir: "Data dir (empty = ~/.memory-tencentdb/memory-tdai)",
101
- fieldLlmBaseUrl: "Base URL",
102
- fieldLlmApiKey: "API Key",
103
- fieldLlmModel: "Model",
104
- fieldLlmMaxTokens: "Max output tokens",
105
- fieldLlmTimeoutMs: "Timeout (ms)",
106
- fieldEmbeddingBaseUrl: "Base URL",
107
- fieldEmbeddingApiKey: "API Key",
108
- fieldEmbeddingModel: "Model",
109
- fieldEmbeddingDimensions: "Vector dimensions",
110
- fieldEmbeddingSendDimensions: "Send dimensions with requests",
111
- fieldCaptureEnabled: "Capture conversations (L0)",
112
- fieldExtractionEnabled: "Structured extraction (L1)",
113
- fieldExtractionEnableDedup: "Conflict detection (extra LLM call)",
114
- fieldRecallEnabled: "Recall injection",
115
- fieldRecallMaxResults: "Max recall results",
116
- fieldRecallScoreThreshold: "Similarity threshold",
117
- fieldRecallTimeoutMs: "Recall timeout (ms)",
118
- fieldToolsEnabled: "Register search tools (tdai_memory_search / tdai_conversation_search)",
119
- secretHint: "Leave blank to keep the current key.",
120
- save: "Save",
121
- reset: "Reset",
122
- saved: "Saved (applies after restart)",
123
- saving: "Saving…",
124
- error: "Save failed",
125
- unavailable: "Settings namespace unavailable (tdai-memory namespace not registered server-side?)",
126
- overridden: "overridden",
127
- loading: "Loading…"
128
- };
129
-
130
- // ── field spec: dotted path + type + group ─────────────────────────────
131
- var FIELDS = [
132
- { path: ["dataDir"], label: "fieldDataDir", type: "text", group: "groupData" },
133
- { path: ["llm", "baseUrl"], label: "fieldLlmBaseUrl", type: "text", group: "groupLlm" },
134
- { path: ["llm", "apiKey"], label: "fieldLlmApiKey", type: "password", secret: true, group: "groupLlm" },
135
- { path: ["llm", "model"], label: "fieldLlmModel", type: "text", group: "groupLlm" },
136
- { path: ["llm", "maxTokens"], label: "fieldLlmMaxTokens", type: "number", group: "groupLlm" },
137
- { path: ["llm", "timeoutMs"], label: "fieldLlmTimeoutMs", type: "number", group: "groupLlm" },
138
- { path: ["embedding", "baseUrl"], label: "fieldEmbeddingBaseUrl", type: "text", group: "groupEmbedding" },
139
- { path: ["embedding", "apiKey"], label: "fieldEmbeddingApiKey", type: "password", secret: true, group: "groupEmbedding" },
140
- { path: ["embedding", "model"], label: "fieldEmbeddingModel", type: "text", group: "groupEmbedding" },
141
- { path: ["embedding", "dimensions"], label: "fieldEmbeddingDimensions", type: "number", group: "groupEmbedding" },
142
- { path: ["embedding", "sendDimensions"], label: "fieldEmbeddingSendDimensions", type: "checkbox", group: "groupEmbedding" },
143
- { path: ["captureEnabled"], label: "fieldCaptureEnabled", type: "checkbox", group: "groupCapture" },
144
- { path: ["extraction", "enabled"], label: "fieldExtractionEnabled", type: "checkbox", group: "groupExtraction" },
145
- { path: ["extraction", "enableDedup"], label: "fieldExtractionEnableDedup", type: "checkbox", group: "groupExtraction" },
146
- { path: ["recall", "enabled"], label: "fieldRecallEnabled", type: "checkbox", group: "groupRecall" },
147
- { path: ["recall", "maxResults"], label: "fieldRecallMaxResults", type: "number", group: "groupRecall" },
148
- { path: ["recall", "scoreThreshold"], label: "fieldRecallScoreThreshold", type: "number", group: "groupRecall" },
149
- { path: ["recall", "timeoutMs"], label: "fieldRecallTimeoutMs", type: "number", group: "groupRecall" },
150
- { path: ["toolsEnabled"], label: "fieldToolsEnabled", type: "checkbox", group: "groupTools" }
151
- ];
152
- FIELDS.forEach(function (f) { f.key = f.path.join("."); });
153
-
154
- function getPath(obj, path) {
155
- var cur = obj;
156
- for (var i = 0; i < path.length; i += 1) {
157
- if (cur === null || cur === void 0 || typeof cur !== "object") return void 0;
158
- cur = cur[path[i]];
159
- }
160
- return cur;
161
- }
162
-
163
- function MemorySection(props) {
164
- var t = props.t;
165
- var scope = props.scope;
166
- var api = props.api;
167
- var [snapshot, setSnapshot] = react.useState(function () { return scope.getSnapshot(); });
168
- var ready = snapshot.status === "ready" && snapshot.value !== void 0;
169
- var [draft, setDraft] = react.useState({});
170
- var [busy, setBusy] = react.useState(false);
171
- var [notice, setNotice] = react.useState(null);
172
- var [error, setError] = react.useState(null);
173
-
174
- react.useEffect(function () {
175
- scope.load();
176
- var alive = true;
177
- var sync = function () { if (alive) setSnapshot(scope.getSnapshot()); };
178
- var un = typeof scope.subscribe === "function" ? scope.subscribe(sync) : null;
179
- return function () { alive = false; if (un) un(); if (scope.dispose) scope.dispose(); };
180
- }, [scope]);
181
- // Initialize the draft ONLY when the snapshot becomes ready — never on
182
- // value churn. settingsScope.getSnapshot() returns a fresh object per
183
- // call, so depending on snapshot.value would reset user input on every
184
- // render (typing appears dead).
185
- react.useEffect(function () {
186
- if (ready) setDraft(Object.assign({}, valueToDraft(snapshot.value)));
187
- // eslint-disable-next-line react-hooks/exhaustive-deps
188
- }, [ready]);
189
-
190
- if (snapshot.status === "unavailable") {
191
- return h("p", { className: "__tm_unavailable" }, t("unavailable"));
192
- }
193
- if (!ready) return h("p", { className: "__tm_status" }, t("loading"));
194
-
195
- var value = snapshot.value;
196
- var user = snapshot.user || {};
197
-
198
- function fieldDraft(f) {
199
- if (f.type === "checkbox") return draft[f.key] !== void 0 ? draft[f.key] : Boolean(getPath(value, f.path));
200
- return draft[f.key] !== void 0 ? draft[f.key] : String(getPath(value, f.path) ?? "");
201
- }
202
- function setField(f, v) {
203
- setDraft(function (prev) { var next = Object.assign({}, prev); next[f.key] = v; return next; });
204
- setNotice(null);
205
- setError(null);
206
- }
207
-
208
- function onSave() {
209
- setBusy(true); setNotice(null); setError(null);
210
- var ops = [];
211
- for (var i = 0; i < FIELDS.length; i += 1) {
212
- var f = FIELDS[i];
213
- var d = fieldDraft(f);
214
- var current = getPath(value, f.path);
215
- if (f.type === "password") {
216
- if (!d) continue; // blank keeps the current key
217
- if (d === String(current ?? "")) continue;
218
- ops.push({ op: "set", path: f.path, value: d });
219
- continue;
220
- }
221
- if (f.type === "checkbox") {
222
- if (Boolean(d) === Boolean(current)) continue;
223
- ops.push(Boolean(d) ? { op: "set", path: f.path, value: true } : { op: "unset", path: f.path });
224
- continue;
225
- }
226
- if (String(d) === String(current ?? "")) continue;
227
- if (String(d).trim() === "" && getPath(user, f.path) === void 0) continue;
228
- ops.push(String(d).trim() === "" ? { op: "unset", path: f.path } : { op: "set", path: f.path, value: f.type === "number" ? Number(d) : d });
229
- }
230
- if (ops.length === 0) { setBusy(false); setNotice(t("saved")); return; }
231
- api.settings.mutate({
232
- ns: "tdai-memory",
233
- ops: ops,
234
- ...snapshot.revision === void 0 ? {} : { expectedRevision: snapshot.revision }
235
- }).then(function (response) {
236
- setBusy(false);
237
- if (!response.result.ok) {
238
- var detail = response.result.error || {};
239
- setError(t("error") + ": " + String(detail.message || detail.code || "unknown"));
240
- return;
241
- }
242
- setNotice(t("saved"));
243
- if (response.result.value) setDraft(Object.assign({}, valueToDraft(response.result.value)));
244
- scope.load();
245
- }).catch(function (e) {
246
- setBusy(false); setError(t("error") + ": " + String(e && e.message || e));
247
- });
248
- }
249
-
250
- function onReset() {
251
- setBusy(true); setNotice(null); setError(null);
252
- api.settings.mutate({
253
- ns: "tdai-memory",
254
- ops: FIELDS.map(function (f) { return { op: "unset", path: f.path }; }),
255
- ...snapshot.revision === void 0 ? {} : { expectedRevision: snapshot.revision }
256
- }).then(function (response) {
257
- setBusy(false);
258
- if (!response.result.ok) { setError(t("error")); return; }
259
- setNotice(t("saved"));
260
- if (response.result.value) setDraft(Object.assign({}, valueToDraft(response.result.value)));
261
- scope.load();
262
- }).catch(function (e) {
263
- setBusy(false); setError(t("error") + ": " + String(e && e.message || e));
264
- });
265
- }
266
-
267
- var nodes = [];
268
- var lastGroup = null;
269
- // forEach callback gives each handler its own `f` — a `for (var i)`
270
- // loop would share one `f` across every onChange closure, so typing
271
- // updated the LAST field's draft and the input appeared dead.
272
- FIELDS.forEach(function (f) {
273
- if (f.group !== lastGroup) {
274
- lastGroup = f.group;
275
- nodes.push(h("div", { key: "g" + f.group, className: "__tm_group" }, t(f.group)));
276
- }
277
- var overridden = getPath(user, f.path) !== void 0;
278
- if (f.type === "checkbox") {
279
- nodes.push(h("label", { key: f.path.join("."), className: "__tm_field" },
280
- h("span", { className: "__tm_row" },
281
- h("input", { className: "__tm_check", type: "checkbox", checked: Boolean(fieldDraft(f)), onChange: function (e) { setField(f, e.target.checked); } }),
282
- h("span", { className: "__tm_label" }, t(f.label)),
283
- overridden ? h("span", { className: "__tm_override" }, t("overridden")) : null
284
- )
285
- ));
286
- return;
287
- }
288
- nodes.push(h("label", { key: f.path.join("."), className: "__tm_field" },
289
- h("span", { className: "__tm_label" },
290
- t(f.label),
291
- overridden ? h("span", { className: "__tm_override" }, t("overridden")) : null
292
- ),
293
- h("input", {
294
- className: "__tm_input",
295
- type: f.type === "password" ? "password" : f.type === "number" ? "number" : "text",
296
- value: fieldDraft(f),
297
- placeholder: f.type === "password" ? (overridden ? "••••••••" : t("secretHint")) : "",
298
- onChange: function (e) { setField(f, e.target.value); }
299
- }),
300
- f.type === "password" ? h("span", { className: "__tm_hint" }, t("secretHint")) : null
301
- ));
302
- });
303
-
304
- return h("div", { className: "__tm_root" },
305
- h("p", { className: "__tm_hint", style: { margin: "0 0 4px" } }, t("intro")),
306
- nodes,
307
- h("div", { className: "__tm_actions" },
308
- h("button", { type: "button", className: "__tm_btn __tm_btnPrimary", onClick: onSave, disabled: busy || !snapshot.writable }, t("save")),
309
- h("button", { type: "button", className: "__tm_btn", onClick: onReset, disabled: busy || !snapshot.writable }, t("reset")),
310
- notice ? h("span", { className: "__tm_status" }, notice) : null,
311
- busy ? h("span", { className: "__tm_status" }, t("saving")) : null,
312
- error ? h("span", { className: "__tm_error" }, error) : null
313
- )
314
- );
315
- }
316
-
317
- function valueToDraft(value) {
318
- var out = {};
319
- for (var i = 0; i < FIELDS.length; i += 1) {
320
- var f = FIELDS[i];
321
- out[f.key] = f.type === "checkbox" ? Boolean(getPath(value, f.path)) : String(getPath(value, f.path) ?? "");
322
- }
323
- return out;
324
- }
325
-
326
- // ── plugin ────────────────────────────────────────────────────────────
327
- function apply(ctx) {
328
- var t = ctx.locale.bind(NS);
329
- ctx.effect(function () { return ctx.locale.register(NS, { zh: zh, en: en }); }, "dsh-tdai-memory: dictionaries");
330
- var scope = ctx.settingsScope.bind({ namespace: "tdai-memory" });
331
- var api = ctx.connection.api;
332
- ctx.slots.inject("settings.section", function () {
333
- return ctx.slots.register({
334
- name: "settings.section",
335
- id: "tdai-memory",
336
- order: 26,
337
- label: function () { return t("nav"); },
338
- locale: NS
339
- }, function (props) {
340
- return h(MemorySection, Object.assign({}, props, { scope: scope, api: api }));
341
- });
342
- });
343
- }
344
-
345
- exports.apply = apply;
346
- exports.inject = inject;
347
- return module.exports;
348
- }
349
- });
1
+ /**
2
+ * dsh-tdai-memory — browser half.
3
+ *
4
+ * A "记忆" section inside the Web UI settings page: edits the `tdai-memory`
5
+ * settings namespace (data dir, extraction LLM, embeddings, capture/extract/
6
+ * recall switches, tools) through the settings scope transport plus nested
7
+ * `settings.mutate` ops. TdaiCore is built at startup, so changes apply
8
+ * after a restart (noted in the UI).
9
+ *
10
+ * Hand-written ModuleLoader bundle — no build step required.
11
+ */
12
+ window.__ModuleLoader__.load({
13
+ id: "dsh-tdai-memory",
14
+ factory: (require) => {
15
+ var module = { exports: {} };
16
+ var exports = module.exports;
17
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
18
+ var react = require("react");
19
+ var h = react.createElement;
20
+
21
+ // ── CSS (theme tokens) ────────────────────────────────────────────────
22
+ var CSS = ".__tm_root{max-width:640px;display:flex;flex-direction:column;gap:10px}" +
23
+ ".__tm_group{font-size:13px;font-weight:700;color:var(--dsw-alias-label-primary);border-bottom:1px solid var(--dsw-alias-border-l2);padding-bottom:4px;margin:6px 0 2px}" +
24
+ ".__tm_field{display:flex;flex-direction:column;gap:4px}" +
25
+ ".__tm_label{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;gap:6px}" +
26
+ ".__tm_override{font-size:10px;color:var(--dsw-alias-state-business-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:4px;padding:0 4px}" +
27
+ ".__tm_hint{font-size:11px;color:var(--dsw-alias-label-tertiary)}" +
28
+ ".__tm_input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:6px 10px;font-size:13px;box-sizing:border-box;width:100%}" +
29
+ ".__tm_row{display:flex;align-items:center;gap:8px}" +
30
+ ".__tm_check{accent-color:var(--dsw-alias-state-business-primary)}" +
31
+ ".__tm_actions{display:flex;gap:8px;align-items:center;margin-top:4px}" +
32
+ ".__tm_btn{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:8px;padding:6px 14px;font:inherit;font-size:13px;cursor:pointer}" +
33
+ ".__tm_btn:hover:not(:disabled){border-color:var(--dsw-alias-state-business-primary)}" +
34
+ ".__tm_btn:disabled{opacity:.5;cursor:default}" +
35
+ ".__tm_btnPrimary{border-color:var(--dsw-alias-state-business-primary, #3964fe);background:var(--dsw-alias-state-business-primary, #3964fe);color:#fff}" +
36
+ ".__tm_status{font-size:12px;color:var(--dsw-alias-label-tertiary)}" +
37
+ ".__tm_error{font-size:12px;color:var(--dsw-alias-state-error-primary)}" +
38
+ ".__tm_unavailable{font-size:13px;color:var(--dsw-alias-label-tertiary)}";
39
+ var tagId = "dsh-tdai-memory/main.css";
40
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
41
+ var tag = document.createElement("style");
42
+ tag.dataset.plugin = "dsh-tdai-memory";
43
+ tag.dataset.pluginCss = tagId;
44
+ tag.textContent = CSS;
45
+ document.head.appendChild(tag);
46
+ }
47
+
48
+ // ── locale ────────────────────────────────────────────────────────────
49
+ var NS = "tdaiMemory";
50
+ var inject = ["slots", "locale", "settingsScope", "connection"];
51
+ var zh = {
52
+ nav: "记忆",
53
+ intro: "TDAI 记忆配置:L0 捕获 → L1 结构化提取 → 召回注入。密钥只写不读。TdaiCore 在启动时构建,修改后需重启生效。",
54
+ groupData: "数据",
55
+ groupLlm: "提取 LLM(L1/L2/L3)",
56
+ groupEmbedding: "向量 Embedding",
57
+ groupCapture: "捕获",
58
+ groupExtraction: "提取",
59
+ groupRecall: "召回",
60
+ groupTools: "工具",
61
+ fieldDataDir: "数据目录(空 = ~/.memory-tencentdb/memory-tdai)",
62
+ fieldLlmBaseUrl: "Base URL",
63
+ fieldLlmApiKey: "API Key",
64
+ fieldLlmModel: "模型",
65
+ fieldLlmMaxTokens: "最大输出 Tokens",
66
+ fieldLlmTimeoutMs: "超时(毫秒)",
67
+ fieldEmbeddingBaseUrl: "Base URL",
68
+ fieldEmbeddingApiKey: "API Key",
69
+ fieldEmbeddingModel: "模型",
70
+ fieldEmbeddingDimensions: "向量维度",
71
+ fieldEmbeddingSendDimensions: "请求带 dimensions 参数",
72
+ fieldCaptureEnabled: "捕获对话(L0)",
73
+ fieldExtractionEnabled: "结构化提取(L1)",
74
+ fieldExtractionEnableDedup: "冲突检测(额外 LLM 调用)",
75
+ fieldRecallEnabled: "召回注入",
76
+ fieldRecallMaxResults: "最大召回条数",
77
+ fieldRecallScoreThreshold: "相似度阈值",
78
+ fieldRecallTimeoutMs: "召回超时(毫秒)",
79
+ fieldToolsEnabled: "注册搜索工具(tdai_memory_search / tdai_conversation_search)",
80
+ secretHint: "留空保持当前密钥。",
81
+ save: "保存",
82
+ reset: "恢复默认",
83
+ saved: "已保存(重启后生效)",
84
+ saving: "保存中…",
85
+ error: "保存失败",
86
+ unavailable: "设置命名空间不可用(服务端未注册 tdai-memory 命名空间?)",
87
+ overridden: "已覆盖",
88
+ loading: "加载中…"
89
+ };
90
+ var en = {
91
+ nav: "Memory",
92
+ intro: "TDAI memory config: L0 capture → L1 extraction → recall injection. Keys are write-only. TdaiCore is built at startup; changes apply after a restart.",
93
+ groupData: "Data",
94
+ groupLlm: "Extraction LLM (L1/L2/L3)",
95
+ groupEmbedding: "Embeddings",
96
+ groupCapture: "Capture",
97
+ groupExtraction: "Extraction",
98
+ groupRecall: "Recall",
99
+ groupTools: "Tools",
100
+ fieldDataDir: "Data dir (empty = ~/.memory-tencentdb/memory-tdai)",
101
+ fieldLlmBaseUrl: "Base URL",
102
+ fieldLlmApiKey: "API Key",
103
+ fieldLlmModel: "Model",
104
+ fieldLlmMaxTokens: "Max output tokens",
105
+ fieldLlmTimeoutMs: "Timeout (ms)",
106
+ fieldEmbeddingBaseUrl: "Base URL",
107
+ fieldEmbeddingApiKey: "API Key",
108
+ fieldEmbeddingModel: "Model",
109
+ fieldEmbeddingDimensions: "Vector dimensions",
110
+ fieldEmbeddingSendDimensions: "Send dimensions with requests",
111
+ fieldCaptureEnabled: "Capture conversations (L0)",
112
+ fieldExtractionEnabled: "Structured extraction (L1)",
113
+ fieldExtractionEnableDedup: "Conflict detection (extra LLM call)",
114
+ fieldRecallEnabled: "Recall injection",
115
+ fieldRecallMaxResults: "Max recall results",
116
+ fieldRecallScoreThreshold: "Similarity threshold",
117
+ fieldRecallTimeoutMs: "Recall timeout (ms)",
118
+ fieldToolsEnabled: "Register search tools (tdai_memory_search / tdai_conversation_search)",
119
+ secretHint: "Leave blank to keep the current key.",
120
+ save: "Save",
121
+ reset: "Reset",
122
+ saved: "Saved (applies after restart)",
123
+ saving: "Saving…",
124
+ error: "Save failed",
125
+ unavailable: "Settings namespace unavailable (tdai-memory namespace not registered server-side?)",
126
+ overridden: "overridden",
127
+ loading: "Loading…"
128
+ };
129
+
130
+ // ── field spec: dotted path + type + group ─────────────────────────────
131
+ var FIELDS = [
132
+ { path: ["dataDir"], label: "fieldDataDir", type: "text", group: "groupData" },
133
+ { path: ["llm", "baseUrl"], label: "fieldLlmBaseUrl", type: "text", group: "groupLlm" },
134
+ { path: ["llm", "apiKey"], label: "fieldLlmApiKey", type: "password", secret: true, group: "groupLlm" },
135
+ { path: ["llm", "model"], label: "fieldLlmModel", type: "text", group: "groupLlm" },
136
+ { path: ["llm", "maxTokens"], label: "fieldLlmMaxTokens", type: "number", group: "groupLlm" },
137
+ { path: ["llm", "timeoutMs"], label: "fieldLlmTimeoutMs", type: "number", group: "groupLlm" },
138
+ { path: ["embedding", "baseUrl"], label: "fieldEmbeddingBaseUrl", type: "text", group: "groupEmbedding" },
139
+ { path: ["embedding", "apiKey"], label: "fieldEmbeddingApiKey", type: "password", secret: true, group: "groupEmbedding" },
140
+ { path: ["embedding", "model"], label: "fieldEmbeddingModel", type: "text", group: "groupEmbedding" },
141
+ { path: ["embedding", "dimensions"], label: "fieldEmbeddingDimensions", type: "number", group: "groupEmbedding" },
142
+ { path: ["embedding", "sendDimensions"], label: "fieldEmbeddingSendDimensions", type: "checkbox", group: "groupEmbedding" },
143
+ { path: ["captureEnabled"], label: "fieldCaptureEnabled", type: "checkbox", group: "groupCapture" },
144
+ { path: ["extraction", "enabled"], label: "fieldExtractionEnabled", type: "checkbox", group: "groupExtraction" },
145
+ { path: ["extraction", "enableDedup"], label: "fieldExtractionEnableDedup", type: "checkbox", group: "groupExtraction" },
146
+ { path: ["recall", "enabled"], label: "fieldRecallEnabled", type: "checkbox", group: "groupRecall" },
147
+ { path: ["recall", "maxResults"], label: "fieldRecallMaxResults", type: "number", group: "groupRecall" },
148
+ { path: ["recall", "scoreThreshold"], label: "fieldRecallScoreThreshold", type: "number", group: "groupRecall" },
149
+ { path: ["recall", "timeoutMs"], label: "fieldRecallTimeoutMs", type: "number", group: "groupRecall" },
150
+ { path: ["toolsEnabled"], label: "fieldToolsEnabled", type: "checkbox", group: "groupTools" }
151
+ ];
152
+ FIELDS.forEach(function (f) { f.key = f.path.join("."); });
153
+
154
+ function getPath(obj, path) {
155
+ var cur = obj;
156
+ for (var i = 0; i < path.length; i += 1) {
157
+ if (cur === null || cur === void 0 || typeof cur !== "object") return void 0;
158
+ cur = cur[path[i]];
159
+ }
160
+ return cur;
161
+ }
162
+
163
+ function MemorySection(props) {
164
+ var t = props.t;
165
+ var scope = props.scope;
166
+ var api = props.api;
167
+ var [snapshot, setSnapshot] = react.useState(function () { return scope.getSnapshot(); });
168
+ var ready = snapshot.status === "ready" && snapshot.value !== void 0;
169
+ var [draft, setDraft] = react.useState({});
170
+ var [busy, setBusy] = react.useState(false);
171
+ var [notice, setNotice] = react.useState(null);
172
+ var [error, setError] = react.useState(null);
173
+
174
+ react.useEffect(function () {
175
+ if (typeof scope.load === "function") scope.load();
176
+ var alive = true;
177
+ var sync = function () { if (alive) setSnapshot(scope.getSnapshot()); };
178
+ var un = typeof scope.subscribe === "function" ? scope.subscribe(sync) : null;
179
+ return function () { alive = false; if (un) un(); if (scope.dispose) scope.dispose(); };
180
+ }, [scope]);
181
+ // Initialize the draft ONLY when the snapshot becomes ready — never on
182
+ // value churn. settingsScope.getSnapshot() returns a fresh object per
183
+ // call, so depending on snapshot.value would reset user input on every
184
+ // render (typing appears dead).
185
+ react.useEffect(function () {
186
+ if (ready) setDraft(Object.assign({}, valueToDraft(snapshot.value)));
187
+ // eslint-disable-next-line react-hooks/exhaustive-deps
188
+ }, [ready]);
189
+
190
+ if (snapshot.status === "unavailable") {
191
+ return h("p", { className: "__tm_unavailable" }, t("unavailable"));
192
+ }
193
+ if (!ready) return h("p", { className: "__tm_status" }, t("loading"));
194
+
195
+ var value = snapshot.value;
196
+ var user = snapshot.user || {};
197
+
198
+ function fieldDraft(f) {
199
+ if (f.type === "checkbox") return draft[f.key] !== void 0 ? draft[f.key] : Boolean(getPath(value, f.path));
200
+ return draft[f.key] !== void 0 ? draft[f.key] : String(getPath(value, f.path) ?? "");
201
+ }
202
+ function setField(f, v) {
203
+ setDraft(function (prev) { var next = Object.assign({}, prev); next[f.key] = v; return next; });
204
+ setNotice(null);
205
+ setError(null);
206
+ }
207
+
208
+ function onSave() {
209
+ setBusy(true); setNotice(null); setError(null);
210
+ var ops = [];
211
+ for (var i = 0; i < FIELDS.length; i += 1) {
212
+ var f = FIELDS[i];
213
+ var d = fieldDraft(f);
214
+ var current = getPath(value, f.path);
215
+ if (f.type === "password") {
216
+ if (!d) continue; // blank keeps the current key
217
+ if (d === String(current ?? "")) continue;
218
+ ops.push({ op: "set", path: f.path, value: d });
219
+ continue;
220
+ }
221
+ if (f.type === "checkbox") {
222
+ if (Boolean(d) === Boolean(current)) continue;
223
+ ops.push(Boolean(d) ? { op: "set", path: f.path, value: true } : { op: "unset", path: f.path });
224
+ continue;
225
+ }
226
+ if (String(d) === String(current ?? "")) continue;
227
+ if (String(d).trim() === "" && getPath(user, f.path) === void 0) continue;
228
+ ops.push(String(d).trim() === "" ? { op: "unset", path: f.path } : { op: "set", path: f.path, value: f.type === "number" ? Number(d) : d });
229
+ }
230
+ if (ops.length === 0) { setBusy(false); setNotice(t("saved")); return; }
231
+ api.settings.mutate({
232
+ ns: "tdai-memory",
233
+ ops: ops,
234
+ ...snapshot.revision === void 0 ? {} : { expectedRevision: snapshot.revision }
235
+ }).then(function (response) {
236
+ setBusy(false);
237
+ if (!response.result.ok) {
238
+ var detail = response.result.error || {};
239
+ setError(t("error") + ": " + String(detail.message || detail.code || "unknown"));
240
+ return;
241
+ }
242
+ setNotice(t("saved"));
243
+ if (response.result.value) setDraft(Object.assign({}, valueToDraft(response.result.value)));
244
+ if (typeof scope.load === "function") scope.load();
245
+ }).catch(function (e) {
246
+ setBusy(false); setError(t("error") + ": " + String(e && e.message || e));
247
+ });
248
+ }
249
+
250
+ function onReset() {
251
+ setBusy(true); setNotice(null); setError(null);
252
+ api.settings.mutate({
253
+ ns: "tdai-memory",
254
+ ops: FIELDS.map(function (f) { return { op: "unset", path: f.path }; }),
255
+ ...snapshot.revision === void 0 ? {} : { expectedRevision: snapshot.revision }
256
+ }).then(function (response) {
257
+ setBusy(false);
258
+ if (!response.result.ok) { setError(t("error")); return; }
259
+ setNotice(t("saved"));
260
+ if (response.result.value) setDraft(Object.assign({}, valueToDraft(response.result.value)));
261
+ if (typeof scope.load === "function") scope.load();
262
+ }).catch(function (e) {
263
+ setBusy(false); setError(t("error") + ": " + String(e && e.message || e));
264
+ });
265
+ }
266
+
267
+ var nodes = [];
268
+ var lastGroup = null;
269
+ // forEach callback gives each handler its own `f` — a `for (var i)`
270
+ // loop would share one `f` across every onChange closure, so typing
271
+ // updated the LAST field's draft and the input appeared dead.
272
+ FIELDS.forEach(function (f) {
273
+ if (f.group !== lastGroup) {
274
+ lastGroup = f.group;
275
+ nodes.push(h("div", { key: "g" + f.group, className: "__tm_group" }, t(f.group)));
276
+ }
277
+ var overridden = getPath(user, f.path) !== void 0;
278
+ if (f.type === "checkbox") {
279
+ nodes.push(h("label", { key: f.path.join("."), className: "__tm_field" },
280
+ h("span", { className: "__tm_row" },
281
+ h("input", { className: "__tm_check", type: "checkbox", checked: Boolean(fieldDraft(f)), onChange: function (e) { setField(f, e.target.checked); } }),
282
+ h("span", { className: "__tm_label" }, t(f.label)),
283
+ overridden ? h("span", { className: "__tm_override" }, t("overridden")) : null
284
+ )
285
+ ));
286
+ return;
287
+ }
288
+ nodes.push(h("label", { key: f.path.join("."), className: "__tm_field" },
289
+ h("span", { className: "__tm_label" },
290
+ t(f.label),
291
+ overridden ? h("span", { className: "__tm_override" }, t("overridden")) : null
292
+ ),
293
+ h("input", {
294
+ className: "__tm_input",
295
+ type: f.type === "password" ? "password" : f.type === "number" ? "number" : "text",
296
+ value: fieldDraft(f),
297
+ placeholder: f.type === "password" ? (overridden ? "••••••••" : t("secretHint")) : "",
298
+ onChange: function (e) { setField(f, e.target.value); }
299
+ }),
300
+ f.type === "password" ? h("span", { className: "__tm_hint" }, t("secretHint")) : null
301
+ ));
302
+ });
303
+
304
+ return h("div", { className: "__tm_root" },
305
+ h("p", { className: "__tm_hint", style: { margin: "0 0 4px" } }, t("intro")),
306
+ nodes,
307
+ h("div", { className: "__tm_actions" },
308
+ h("button", { type: "button", className: "__tm_btn __tm_btnPrimary", onClick: onSave, disabled: busy || !snapshot.writable }, t("save")),
309
+ h("button", { type: "button", className: "__tm_btn", onClick: onReset, disabled: busy || !snapshot.writable }, t("reset")),
310
+ notice ? h("span", { className: "__tm_status" }, notice) : null,
311
+ busy ? h("span", { className: "__tm_status" }, t("saving")) : null,
312
+ error ? h("span", { className: "__tm_error" }, error) : null
313
+ )
314
+ );
315
+ }
316
+
317
+ function valueToDraft(value) {
318
+ var out = {};
319
+ for (var i = 0; i < FIELDS.length; i += 1) {
320
+ var f = FIELDS[i];
321
+ out[f.key] = f.type === "checkbox" ? Boolean(getPath(value, f.path)) : String(getPath(value, f.path) ?? "");
322
+ }
323
+ return out;
324
+ }
325
+
326
+ // ── plugin ────────────────────────────────────────────────────────────
327
+ function apply(ctx) {
328
+ var t = ctx.locale.bind(NS);
329
+ ctx.effect(function () { return ctx.locale.register(NS, { zh: zh, en: en }); }, "dsh-tdai-memory: dictionaries");
330
+ var scope = ctx.settingsScope.bind({ namespace: "tdai-memory" });
331
+ var api = ctx.connection.api;
332
+ ctx.slots.inject("settings.section", function () {
333
+ return ctx.slots.register({
334
+ name: "settings.section",
335
+ id: "tdai-memory",
336
+ order: 26,
337
+ label: function () { return t("nav"); },
338
+ locale: NS
339
+ }, function (props) {
340
+ return h(MemorySection, Object.assign({}, props, { scope: scope, api: api }));
341
+ });
342
+ });
343
+ }
344
+
345
+ exports.apply = apply;
346
+ exports.inject = inject;
347
+ return module.exports;
348
+ }
349
+ });
350
+