dsh-tdai-memory 0.2.9 → 0.2.11

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