dsh-hypatia-auto-memory 0.1.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.
package/lib/client.js ADDED
@@ -0,0 +1,1081 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-hypatia-auto-memory",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+ //#region src/client/consolidation-models.ts
11
+ /** Stable opaque key for one exact route. */
12
+ function consolidationModelKey(route) {
13
+ return `${route.provider}\0${route.model}`;
14
+ }
15
+ /**
16
+ * Join the live model directory with selected routes that disappeared from it.
17
+ * Disappeared routes remain visible so users can remove them deliberately.
18
+ */
19
+ function consolidationModelCandidates(groups, stored, selected) {
20
+ const storedByKey = new Map(stored.map((route) => [consolidationModelKey(route), route]));
21
+ const candidates = groups.flatMap((group) => group.models.map((model) => {
22
+ const route = {
23
+ provider: group.id,
24
+ model: model.id
25
+ };
26
+ const key = consolidationModelKey(route);
27
+ storedByKey.delete(key);
28
+ return {
29
+ ...route,
30
+ key,
31
+ providerName: group.name,
32
+ modelName: model.name,
33
+ available: true,
34
+ selected: selected.has(key)
35
+ };
36
+ }));
37
+ for (const route of storedByKey.values()) {
38
+ const key = consolidationModelKey(route);
39
+ candidates.push({
40
+ ...route,
41
+ key,
42
+ providerName: route.provider,
43
+ modelName: route.model,
44
+ available: false,
45
+ selected: selected.has(key)
46
+ });
47
+ }
48
+ return candidates;
49
+ }
50
+ function isRecord(value) {
51
+ return typeof value === "object" && value !== null && !Array.isArray(value);
52
+ }
53
+ /**
54
+ * Pick the inventory namespace out of a settings descriptor.
55
+ *
56
+ * Reads the composition layer (`base`), which only the Host sets, rather than
57
+ * the resolved value: a stray user-layer section for this namespace would
58
+ * otherwise freeze the list.
59
+ * @param described - the `namespaces` array a settings `describe()` returns.
60
+ * @returns the listing, or undefined when the Host does not serve one.
61
+ */
62
+ function readShelfInventory(described) {
63
+ const view = described.find((entry) => isRecord(entry) && entry.ns === "hypatia-auto-memory-shelves");
64
+ if (!isRecord(view) || !isRecord(view.base)) return void 0;
65
+ const { shelves, error } = view.base;
66
+ return {
67
+ shelves: Array.isArray(shelves) ? shelves.flatMap((shelf) => isRecord(shelf) && typeof shelf.name === "string" ? [{
68
+ name: shelf.name,
69
+ path: typeof shelf.path === "string" ? shelf.path : "",
70
+ connected: shelf.connected === true
71
+ }] : []) : [],
72
+ error: typeof error === "string" ? error : ""
73
+ };
74
+ }
75
+ /**
76
+ * Every shelf the dropdown offers: the listing, plus the stored and composed
77
+ * values when the listing does not carry them, so the current choice always
78
+ * renders and never silently changes to the first option.
79
+ * @param shelves - the Host's listing.
80
+ * @param keep - values that must stay selectable (current draft, stored, composed).
81
+ * @returns options, listed shelves first in listing order.
82
+ */
83
+ function shelfChoices(shelves, keep) {
84
+ const choices = shelves.map((shelf) => ({
85
+ ...shelf,
86
+ listed: true
87
+ }));
88
+ for (const name of keep) {
89
+ if (name === "" || choices.some((choice) => choice.name === name)) continue;
90
+ choices.push({
91
+ name,
92
+ path: "",
93
+ connected: false,
94
+ listed: false
95
+ });
96
+ }
97
+ return choices;
98
+ }
99
+ //#endregion
100
+ //#region \0dsh-hypatia-css:/Users/baihaoran/Code/github.com/tkliuxing/dsh-hypatia-auto-memory/src/client/SettingsCard.module.css.mjs
101
+ const css = ".MMni7a_card{border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-3);border-radius:16px;list-style:none;transition:border-color .16s,background .16s}.MMni7a_card:hover{border-color:var(--dsw-alias-label-dimmed)}.MMni7a_cardOpen{border-color:var(--dsw-alias-label-dimmed);background:var(--dsw-alias-bg-layer-2)}.MMni7a_header{appearance:none;width:100%;color:inherit;cursor:pointer;font:inherit;text-align:left;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.MMni7a_header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}.MMni7a_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.MMni7a_name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.MMni7a_description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.MMni7a_pending{corner-shape:round;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:999px;flex:none;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}.MMni7a_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.MMni7a_chevronOpen{transform:rotate(180deg)}.MMni7a_body{border-top:.5px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}.MMni7a_status,.MMni7a_readOnly,.MMni7a_hint,.MMni7a_advancedHint,.MMni7a_error{margin:0;font-size:12px;line-height:1.5}.MMni7a_status,.MMni7a_readOnly,.MMni7a_hint,.MMni7a_advancedHint{color:var(--dsw-alias-label-tertiary)}.MMni7a_status{padding:12px 16px;list-style:none}.MMni7a_readOnly{padding-top:12px}.MMni7a_field{gap:6px;padding:12px 0;display:grid}.MMni7a_body>.MMni7a_field+.MMni7a_group,.MMni7a_body>.MMni7a_group+.MMni7a_field{border-top:.5px solid var(--dsw-alias-border-l2)}.MMni7a_group{padding:12px 0}.MMni7a_groupTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:13px;font-weight:500;line-height:1.5}.MMni7a_limitsTitle{border-top:.5px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);margin:16px 0 0;padding-top:12px;font-size:13px;font-weight:500;line-height:1.5}.MMni7a_limitsTitle+.MMni7a_hint{margin-top:4px}.MMni7a_group>.MMni7a_field:first-of-type{padding-top:12px}.MMni7a_group>.MMni7a_field+.MMni7a_field,.MMni7a_pairedFields{border-top:.5px solid var(--dsw-alias-border-l2)}.MMni7a_fieldHead{align-items:center;gap:8px;display:flex}.MMni7a_label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}.MMni7a_reset{color:var(--dsw-alias-label-secondary);cursor:pointer;font:inherit;background:0 0;border:0;padding:0;font-size:12px;line-height:1.5}.MMni7a_reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.MMni7a_reset:disabled{cursor:default}.MMni7a_input{box-sizing:border-box;width:100%;min-width:0;display:flex}.MMni7a_select{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);width:100%;min-width:0;height:32px;color:var(--dsw-alias-label-primary);font:inherit;background:0 0;border-radius:8px;padding:0 8px;font-size:13px}.MMni7a_select:disabled{opacity:.5}.MMni7a_select:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.MMni7a_warning{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}.MMni7a_pairedFields{grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin:4px 0;padding:4px 0;display:grid}.MMni7a_pairedFields .MMni7a_field{min-width:0}.MMni7a_switch{box-sizing:border-box;background:var(--dsw-alias-border-l3);cursor:pointer;border:0;border-radius:10px;width:36px;height:20px;padding:2px;position:relative}.MMni7a_switchOn{background:var(--dsw-alias-brand-primary)}.MMni7a_switch:disabled{cursor:default;opacity:.5}.MMni7a_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}.MMni7a_switchThumb{corner-shape:round;background:var(--dsw-alias-label-primary-foreground);border-radius:50%;width:16px;height:16px;transition:transform .12s;display:block}.MMni7a_switchOn .MMni7a_switchThumb{transform:translate(16px)}.MMni7a_modelSelection{gap:10px;padding:12px 0;display:grid}.MMni7a_notice{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}.MMni7a_catalogError{color:var(--dsw-alias-label-error);justify-content:space-between;align-items:center;gap:12px;font-size:12px;line-height:1.5;display:flex}.MMni7a_catalogError button{color:var(--dsw-alias-brand-primary);cursor:pointer;font:inherit;background:0 0;border:0;padding:0}.MMni7a_models{border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;gap:6px;min-width:0;max-height:280px;margin:0;padding:10px;display:grid;overflow:auto}.MMni7a_models legend{color:var(--dsw-alias-label-secondary);padding:0 4px;font-size:12px}.MMni7a_modelGroup{gap:6px;display:grid}.MMni7a_modelGroup+.MMni7a_modelGroup{border-top:.5px solid var(--dsw-alias-border-l3);margin-top:4px;padding-top:10px}.MMni7a_providerName{color:var(--dsw-alias-label-tertiary);padding:0 6px;font-size:11px;font-weight:500}.MMni7a_model{cursor:pointer;border-radius:6px;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:8px;min-width:0;padding:6px;display:grid}.MMni7a_model:hover{background:var(--dsw-alias-bg-layer-4)}.MMni7a_modelName,.MMni7a_route{text-overflow:ellipsis;white-space:nowrap;display:block;overflow:hidden}.MMni7a_modelName{color:var(--dsw-alias-label-primary);font-size:13px}.MMni7a_route{color:var(--dsw-alias-label-tertiary);margin-top:2px;font-size:11px}.MMni7a_unavailable{color:var(--dsw-alias-label-tertiary);font-size:11px}.MMni7a_error{color:var(--dsw-alias-label-error)}.MMni7a_advancedHint{border-top:.5px solid var(--dsw-alias-border-l2);padding-top:12px}.MMni7a_footer{border-top:.5px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}@media (width<=480px){.MMni7a_pairedFields{grid-template-columns:minmax(0,1fr);gap:0}.MMni7a_pairedFields .MMni7a_field+.MMni7a_field{border-top:.5px solid var(--dsw-alias-border-l2)}}";
102
+ const tagId = "dsh-hypatia-auto-memory/SettingsCard.module.css";
103
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
104
+ const tag = document.createElement("style");
105
+ tag.dataset.plugin = "dsh-hypatia-auto-memory";
106
+ tag.dataset.pluginCss = tagId;
107
+ tag.textContent = css;
108
+ document.head.appendChild(tag);
109
+ }
110
+ var SettingsCard_module_css_default = {
111
+ "label": "MMni7a_label",
112
+ "switchOn": "MMni7a_switchOn",
113
+ "route": "MMni7a_route",
114
+ "group": "MMni7a_group",
115
+ "pairedFields": "MMni7a_pairedFields",
116
+ "reset": "MMni7a_reset",
117
+ "input": "MMni7a_input",
118
+ "model": "MMni7a_model",
119
+ "card": "MMni7a_card",
120
+ "groupTitle": "MMni7a_groupTitle",
121
+ "switchThumb": "MMni7a_switchThumb",
122
+ "header": "MMni7a_header",
123
+ "pending": "MMni7a_pending",
124
+ "models": "MMni7a_models",
125
+ "fieldHead": "MMni7a_fieldHead",
126
+ "modelSelection": "MMni7a_modelSelection",
127
+ "modelGroup": "MMni7a_modelGroup",
128
+ "body": "MMni7a_body",
129
+ "readOnly": "MMni7a_readOnly",
130
+ "cardOpen": "MMni7a_cardOpen",
131
+ "headText": "MMni7a_headText",
132
+ "limitsTitle": "MMni7a_limitsTitle",
133
+ "select": "MMni7a_select",
134
+ "modelName": "MMni7a_modelName",
135
+ "catalogError": "MMni7a_catalogError",
136
+ "chevron": "MMni7a_chevron",
137
+ "error": "MMni7a_error",
138
+ "footer": "MMni7a_footer",
139
+ "description": "MMni7a_description",
140
+ "status": "MMni7a_status",
141
+ "warning": "MMni7a_warning",
142
+ "notice": "MMni7a_notice",
143
+ "advancedHint": "MMni7a_advancedHint",
144
+ "providerName": "MMni7a_providerName",
145
+ "hint": "MMni7a_hint",
146
+ "chevronOpen": "MMni7a_chevronOpen",
147
+ "name": "MMni7a_name",
148
+ "field": "MMni7a_field",
149
+ "switch": "MMni7a_switch",
150
+ "unavailable": "MMni7a_unavailable"
151
+ };
152
+ //#endregion
153
+ //#region src/client/locales.ts
154
+ /** `hypatia-auto-memory` namespace dictionaries. */
155
+ /** Dictionary namespace owned by this plugin. */
156
+ const NS = "hypatia-auto-memory";
157
+ /** Simplified Chinese dictionary and key source of truth. */
158
+ const zh = {
159
+ loading: "正在加载 {ns} 设置…",
160
+ unavailable: "当前上下文不可用 {ns} 设置。",
161
+ title: "Hypatia Auto Memory",
162
+ description: "自动将对话轮次记录到 Hypatia,并在新内容足够多时通过独立模型路由进行后台整合。",
163
+ enable: "启用自动记忆",
164
+ autoApprove: "自动批准模型发起的 hypatia 命令",
165
+ autoApproveHint: "仅限不含管道、重定向、命令串联的纯 hypatia 调用;插件自身的写入不经过此路径。关闭后模型每次检索都会弹出批准框。修改后需重载配置档生效。",
166
+ shelf: "记忆写入的 shelf",
167
+ shelfHint: "插件的记录、整合与会话开始时的规则预加载都读写这个 shelf;不是 default 时,会话开头会提示模型为 hypatia 命令加上 --shelf。修改后需重载配置档生效。每个 shelf 的记录进度单独保存,切回原来的 shelf 会从上次的位置继续;切到新的 shelf 后,每个有活动的会话会把完整历史重新记录进去并整合一遍(会消耗模型调用)。列表来自 hypatia list,约每分钟刷新一次。",
168
+ shelfLoading: "正在读取 hypatia list…",
169
+ shelfLoadFailed: "无法读取 shelf 列表。",
170
+ shelfListingFailed: "hypatia list 执行失败,列表可能已过时:{message}",
171
+ shelfDisconnected: "未连接",
172
+ shelfOptionStatus: "({status})",
173
+ shelfNotListed: "不在 hypatia list 中",
174
+ shelfDisconnectedWarning: "shelf {shelf} 已注册但未连接,写入会失败。",
175
+ shelfNotListedWarning: "hypatia 没有注册 shelf {shelf},写入会失败;请先执行 hypatia connect <目录> --name {shelf}。",
176
+ consolidationTitle: "整合候选模型",
177
+ modelSelectionHint: "选择一个或多个模型。每次整合尝试(含重试)会按所选顺序轮转;未选择时只记录对话,不执行整合。",
178
+ modelCatalogLoading: "正在读取可用模型…",
179
+ modelCatalogFailed: "无法读取可用模型。",
180
+ modelCatalogPartial: "部分提供方未能返回模型目录。",
181
+ modelCatalogEmpty: "当前没有可选择的模型。",
182
+ selectedModels: "用于轮转的模型",
183
+ unavailableModels: "不可用或未再声明的模型",
184
+ modelUnavailable: "当前不可用",
185
+ retry: "重试",
186
+ checkEveryTurns: "两次整合至少间隔 N 轮",
187
+ minNewTokens: "最小新增 token 数",
188
+ cascadeBatchSize: "归档批量",
189
+ dedupMaxDistance: "关联距离上限",
190
+ cascadeHint: "每积累这么多同层摘要,就归档为上一层的一条;关联距离上限决定新记忆要多接近既有条目才值得判定关系。",
191
+ limitsTitle: "单次整合体量",
192
+ limitsHint: "输入预算决定送入模型的对话长度(超出部分截断保留最近内容);输出预算需容纳摘要与全部工作单元,过小会导致整合失败。",
193
+ maxInputTokens: "输入预算(token)",
194
+ maxOutputTokens: "输出预算(token)",
195
+ maxWorkUnitsPerRun: "每次最多提取工作单元数",
196
+ recallPreload: "召回:会话开始时预加载规则与禁忌",
197
+ advancedHint: "高级字段(二进制、收集器上限、队列调优)可直接在 settings.yaml 的 {ns} 键下编辑。",
198
+ reset: "重置",
199
+ discard: "放弃",
200
+ save: "保存",
201
+ saving: "保存中…",
202
+ saveFailed: "保存失败:{message}",
203
+ readOnly: "只读:当前上下文不可写入设置文档。",
204
+ unsaved: "未保存"
205
+ };
206
+ /** English dictionary checked against the Chinese key set. */
207
+ const en = {
208
+ loading: "Loading {ns} settings…",
209
+ unavailable: "{ns} settings are not available in this context.",
210
+ title: "Hypatia Auto Memory",
211
+ description: "Auto-memory logs conversation turns into Hypatia and runs background consolidation on a dedicated model route.",
212
+ enable: "Enable auto-memory",
213
+ autoApprove: "Auto-approve the agent's own hypatia commands",
214
+ autoApproveHint: "Only plain hypatia calls with no pipe, redirect or chaining; this plugin's own writes never take that path. With it off, every retrieval raises an approval prompt. Takes effect after a profile reload.",
215
+ shelf: "Memory shelf",
216
+ shelfHint: "Logging, consolidation and the session-start rules preload all read and write this shelf; when it is not default, each session is told to pass --shelf to its hypatia commands. Takes effect after a profile reload. Progress is kept per shelf, so switching back resumes where that shelf left off; on a new shelf, each session is logged from its start the next time it is active, and consolidated again (model calls). Choices come from hypatia list, refreshed about once a minute.",
217
+ shelfLoading: "Reading hypatia list…",
218
+ shelfLoadFailed: "Unable to load the shelf list.",
219
+ shelfListingFailed: "hypatia list failed; the list may be stale: {message}",
220
+ shelfDisconnected: "not connected",
221
+ shelfOptionStatus: " ({status})",
222
+ shelfNotListed: "not in hypatia list",
223
+ shelfDisconnectedWarning: "Shelf {shelf} is registered but not connected; writes will fail.",
224
+ shelfNotListedWarning: "hypatia has no shelf named {shelf}; writes will fail until you run hypatia connect <dir> --name {shelf}.",
225
+ consolidationTitle: "Consolidation candidate models",
226
+ modelSelectionHint: "Choose one or more models. Every consolidation attempt, including a retry, rotates through this order; without a selection, conversations continue logging but consolidation stays idle.",
227
+ modelCatalogLoading: "Loading available models…",
228
+ modelCatalogFailed: "Unable to load available models.",
229
+ modelCatalogPartial: "Some providers did not return a model catalog.",
230
+ modelCatalogEmpty: "No models are currently available.",
231
+ selectedModels: "Models used for rotation",
232
+ unavailableModels: "Unavailable or no longer advertised",
233
+ modelUnavailable: "Unavailable",
234
+ retry: "Retry",
235
+ checkEveryTurns: "Minimum turns between consolidations",
236
+ minNewTokens: "Min new tokens",
237
+ cascadeBatchSize: "Archive batch",
238
+ dedupMaxDistance: "Relation distance",
239
+ cascadeHint: "Every batch of same-tier summaries is archived into one entry a tier up. Relation distance is how near a new memory must be to an existing one to be worth judging a relationship.",
240
+ limitsTitle: "Per-run consolidation size",
241
+ limitsHint: "The input budget caps how much conversation is sent to the model (overflow keeps the most recent content); the output budget must fit the summary plus every work unit — too small and consolidation fails.",
242
+ maxInputTokens: "Input budget (tokens)",
243
+ maxOutputTokens: "Output budget (tokens)",
244
+ maxWorkUnitsPerRun: "Max work units per run",
245
+ recallPreload: "Recall: preload rules & taboos at session start",
246
+ advancedHint: "Advanced fields (binaries, collector caps, queue tuning) can be edited directly in settings.yaml under the {ns} key.",
247
+ reset: "Reset",
248
+ discard: "Discard",
249
+ save: "Save",
250
+ saving: "Saving…",
251
+ saveFailed: "Save failed: {message}",
252
+ readOnly: "Read-only: settings document is not writable in this context.",
253
+ unsaved: "Unsaved"
254
+ };
255
+ //#endregion
256
+ //#region src/client/SettingsCard.tsx
257
+ /**
258
+ * Settings card for the `hypatia-auto-memory` namespace.
259
+ *
260
+ * The card owns its disclosure state and stages edits until Save. Its visual
261
+ * treatment mirrors the DSH plugin-settings cards while remaining bundle-local.
262
+ */
263
+ const DEFAULT_CONSOLIDATION = {
264
+ models: [],
265
+ maxInputTokens: 16e3,
266
+ maxOutputTokens: 2e3,
267
+ timeoutMs: 12e4,
268
+ checkEveryTurns: 5,
269
+ minNewTokens: 3e3,
270
+ maxWorkUnitsPerRun: 3,
271
+ adjudicate: true,
272
+ dedupMaxDistance: .45,
273
+ cascade: {
274
+ enabled: true,
275
+ batchSize: 16
276
+ }
277
+ };
278
+ function getSnapshotValue(scope) {
279
+ return scope.getSnapshot();
280
+ }
281
+ function subscribe(scope, cb) {
282
+ return scope.subscribe(cb);
283
+ }
284
+ function sectionValue(snap) {
285
+ return snap.value ?? snap.base ?? void 0;
286
+ }
287
+ function useScopeValue(scope) {
288
+ const [snap, setSnap] = (0, react.useState)(() => getSnapshotValue(scope));
289
+ (0, react.useEffect)(() => {
290
+ setSnap(getSnapshotValue(scope));
291
+ return subscribe(scope, () => setSnap(getSnapshotValue(scope)));
292
+ }, [scope]);
293
+ return {
294
+ snap,
295
+ value: (0, react.useMemo)(() => sectionValue(snap), [snap])
296
+ };
297
+ }
298
+ function same(a, b) {
299
+ return JSON.stringify(a) === JSON.stringify(b);
300
+ }
301
+ function SettingsCard({ scope, loadModelCatalog, loadShelfInventory, t }) {
302
+ const { snap, value } = useScopeValue(scope);
303
+ const disabled = !snap.writable;
304
+ const base = (0, react.useMemo)(() => snap.base ?? {}, [snap.base]);
305
+ const resolved = (0, react.useMemo)(() => value ?? base ?? {
306
+ enabled: true,
307
+ autoApprove: true,
308
+ shelf: "default",
309
+ consolidation: DEFAULT_CONSOLIDATION,
310
+ recall: { preloadRulesTaboos: true }
311
+ }, [value, base]);
312
+ const [draft, setDraft] = (0, react.useState)(resolved);
313
+ const [open, setOpen] = (0, react.useState)(false);
314
+ const [saving, setSaving] = (0, react.useState)(false);
315
+ const [error, setError] = (0, react.useState)(null);
316
+ const [catalogStatus, setCatalogStatus] = (0, react.useState)("idle");
317
+ const [catalogGroups, setCatalogGroups] = (0, react.useState)([]);
318
+ const [catalogPartial, setCatalogPartial] = (0, react.useState)(false);
319
+ const catalogGeneration = (0, react.useRef)(0);
320
+ const [shelfStatus, setShelfStatus] = (0, react.useState)("idle");
321
+ const [shelves, setShelves] = (0, react.useState)([]);
322
+ const [shelfListingError, setShelfListingError] = (0, react.useState)("");
323
+ const shelfGeneration = (0, react.useRef)(0);
324
+ (0, react.useEffect)(() => {
325
+ setDraft(resolved);
326
+ }, [resolved]);
327
+ const dirty = (0, react.useMemo)(() => !same(draft, resolved), [draft, resolved]);
328
+ const consolidation = draft.consolidation ?? DEFAULT_CONSOLIDATION;
329
+ const recall = draft.recall ?? { preloadRulesTaboos: true };
330
+ const saveBlocked = disabled || !dirty || saving;
331
+ const updateConsolidation = (0, react.useCallback)((patch) => {
332
+ setDraft((prev) => ({
333
+ ...prev,
334
+ consolidation: {
335
+ ...prev.consolidation ?? DEFAULT_CONSOLIDATION,
336
+ ...patch
337
+ }
338
+ }));
339
+ }, []);
340
+ const loadCatalog = (0, react.useCallback)(async () => {
341
+ const generation = ++catalogGeneration.current;
342
+ setCatalogStatus("loading");
343
+ setCatalogPartial(false);
344
+ try {
345
+ const catalog = await loadModelCatalog();
346
+ if (generation !== catalogGeneration.current) return;
347
+ setCatalogGroups(catalog.groups);
348
+ setCatalogPartial(catalog.partial);
349
+ setCatalogStatus("ready");
350
+ } catch {
351
+ if (generation !== catalogGeneration.current) return;
352
+ setCatalogStatus("error");
353
+ }
354
+ }, [loadModelCatalog]);
355
+ (0, react.useEffect)(() => {
356
+ if (!open) return;
357
+ loadCatalog();
358
+ return () => {
359
+ catalogGeneration.current += 1;
360
+ };
361
+ }, [open, loadCatalog]);
362
+ const loadShelves = (0, react.useCallback)(async () => {
363
+ const generation = ++shelfGeneration.current;
364
+ setShelfStatus("loading");
365
+ try {
366
+ const inventory = await loadShelfInventory();
367
+ if (generation !== shelfGeneration.current) return;
368
+ setShelves(inventory.shelves);
369
+ setShelfListingError(inventory.error);
370
+ setShelfStatus("ready");
371
+ } catch {
372
+ if (generation !== shelfGeneration.current) return;
373
+ setShelfStatus("error");
374
+ }
375
+ }, [loadShelfInventory]);
376
+ (0, react.useEffect)(() => {
377
+ if (!open) return;
378
+ loadShelves();
379
+ return () => {
380
+ shelfGeneration.current += 1;
381
+ };
382
+ }, [open, loadShelves]);
383
+ const draftShelf = draft.shelf ?? "default";
384
+ const baseShelf = base.shelf ?? "default";
385
+ const choices = (0, react.useMemo)(() => shelfChoices(shelves, [
386
+ draftShelf,
387
+ resolved.shelf ?? "default",
388
+ baseShelf
389
+ ]), [
390
+ shelves,
391
+ draftShelf,
392
+ resolved.shelf,
393
+ baseShelf
394
+ ]);
395
+ const chosen = choices.find((choice) => choice.name === draftShelf);
396
+ const candidates = (0, react.useMemo)(() => {
397
+ const models = consolidation.models ?? [];
398
+ return consolidationModelCandidates(catalogGroups, models, new Set(models.map(consolidationModelKey)));
399
+ }, [catalogGroups, consolidation.models]);
400
+ const toggleModel = (0, react.useCallback)((candidate) => {
401
+ if (disabled || saving) return;
402
+ setDraft((current) => {
403
+ const currentConsolidation = current.consolidation ?? DEFAULT_CONSOLIDATION;
404
+ const currentModels = currentConsolidation.models ?? [];
405
+ const key = consolidationModelKey(candidate);
406
+ const models = currentModels.some((route) => consolidationModelKey(route) === key) ? currentModels.filter((route) => consolidationModelKey(route) !== key) : [...currentModels, {
407
+ provider: candidate.provider,
408
+ model: candidate.model
409
+ }];
410
+ return {
411
+ ...current,
412
+ consolidation: {
413
+ ...currentConsolidation,
414
+ models
415
+ }
416
+ };
417
+ });
418
+ }, [disabled, saving]);
419
+ const discard = (0, react.useCallback)(() => {
420
+ setDraft(resolved);
421
+ setError(null);
422
+ }, [resolved]);
423
+ const save = (0, react.useCallback)(async () => {
424
+ if (saveBlocked) return;
425
+ setSaving(true);
426
+ setError(null);
427
+ try {
428
+ const tasks = [];
429
+ if (!same(draft.enabled, resolved.enabled)) tasks.push(scope.set("enabled", draft.enabled));
430
+ if (!same(draft.autoApprove, resolved.autoApprove)) tasks.push(scope.set("autoApprove", draft.autoApprove));
431
+ if (!same(draft.shelf, resolved.shelf)) tasks.push(scope.set("shelf", draft.shelf));
432
+ if (!same(draft.consolidation, resolved.consolidation)) tasks.push(scope.set("consolidation", draft.consolidation));
433
+ if (!same(draft.recall, resolved.recall)) tasks.push(scope.set("recall", draft.recall));
434
+ await Promise.all(tasks);
435
+ } catch (err) {
436
+ const message = err instanceof Error ? err.message : String(err);
437
+ console.error(`[${NS}] save failed:`, err);
438
+ setError(message);
439
+ } finally {
440
+ setSaving(false);
441
+ }
442
+ }, [
443
+ draft,
444
+ resolved,
445
+ saveBlocked,
446
+ scope
447
+ ]);
448
+ if (snap.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
449
+ className: SettingsCard_module_css_default.status,
450
+ children: t("loading", { ns: NS })
451
+ });
452
+ if (snap.status === "unavailable") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
453
+ className: SettingsCard_module_css_default.status,
454
+ children: t("unavailable", { ns: NS })
455
+ });
456
+ const availableGroups = /* @__PURE__ */ new Map();
457
+ const unavailable = [];
458
+ for (const candidate of candidates) {
459
+ if (!candidate.available) {
460
+ unavailable.push(candidate);
461
+ continue;
462
+ }
463
+ const group = availableGroups.get(candidate.provider);
464
+ if (group === void 0) availableGroups.set(candidate.provider, {
465
+ providerName: candidate.providerName,
466
+ candidates: [candidate]
467
+ });
468
+ else group.candidates.push(candidate);
469
+ }
470
+ const renderCandidate = (candidate) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
471
+ className: SettingsCard_module_css_default.model,
472
+ children: [
473
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
474
+ type: "checkbox",
475
+ checked: candidate.selected,
476
+ disabled: disabled || saving,
477
+ onChange: () => toggleModel(candidate)
478
+ }),
479
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
480
+ className: SettingsCard_module_css_default.modelName,
481
+ children: candidate.modelName
482
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
483
+ className: SettingsCard_module_css_default.route,
484
+ children: `${candidate.providerName} · ${candidate.provider}/${candidate.model}`
485
+ })] }),
486
+ !candidate.available ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
487
+ className: SettingsCard_module_css_default.unavailable,
488
+ children: t("modelUnavailable")
489
+ }) : null
490
+ ]
491
+ }, candidate.key);
492
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
493
+ className: `${SettingsCard_module_css_default.card} ${open ? SettingsCard_module_css_default.cardOpen : ""}`,
494
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
495
+ type: "button",
496
+ className: SettingsCard_module_css_default.header,
497
+ onClick: () => setOpen(!open),
498
+ "aria-expanded": open,
499
+ children: [
500
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
501
+ className: SettingsCard_module_css_default.headText,
502
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
503
+ className: SettingsCard_module_css_default.name,
504
+ children: t("title")
505
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
506
+ className: SettingsCard_module_css_default.description,
507
+ children: t("description")
508
+ })]
509
+ }),
510
+ dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
511
+ className: SettingsCard_module_css_default.pending,
512
+ children: t("unsaved")
513
+ }) : null,
514
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: `${SettingsCard_module_css_default.chevron} ${open ? SettingsCard_module_css_default.chevronOpen : ""}` })
515
+ ]
516
+ }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
517
+ className: SettingsCard_module_css_default.body,
518
+ children: [
519
+ !snap.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
520
+ className: SettingsCard_module_css_default.readOnly,
521
+ role: "status",
522
+ children: t("readOnly")
523
+ }) : null,
524
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
525
+ className: SettingsCard_module_css_default.field,
526
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
527
+ className: SettingsCard_module_css_default.fieldHead,
528
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
529
+ className: SettingsCard_module_css_default.label,
530
+ htmlFor: "ham-enabled",
531
+ children: t("enable")
532
+ }), draft.enabled !== (base.enabled ?? true) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
533
+ type: "button",
534
+ className: SettingsCard_module_css_default.reset,
535
+ disabled,
536
+ onClick: () => setDraft((current) => ({
537
+ ...current,
538
+ enabled: base.enabled ?? true
539
+ })),
540
+ children: t("reset")
541
+ }) : null]
542
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
543
+ id: "ham-enabled",
544
+ type: "button",
545
+ role: "switch",
546
+ "aria-checked": draft.enabled ?? true,
547
+ "aria-label": t("enable"),
548
+ className: `${SettingsCard_module_css_default.switch} ${draft.enabled ?? true ? SettingsCard_module_css_default.switchOn : ""}`,
549
+ disabled,
550
+ onClick: () => setDraft((current) => ({
551
+ ...current,
552
+ enabled: !(current.enabled ?? true)
553
+ })),
554
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: SettingsCard_module_css_default.switchThumb })
555
+ })]
556
+ }),
557
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
558
+ className: SettingsCard_module_css_default.field,
559
+ children: [
560
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
561
+ className: SettingsCard_module_css_default.fieldHead,
562
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
563
+ className: SettingsCard_module_css_default.label,
564
+ htmlFor: "ham-autoApprove",
565
+ children: t("autoApprove")
566
+ }), (draft.autoApprove ?? true) !== (base.autoApprove ?? true) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
567
+ type: "button",
568
+ className: SettingsCard_module_css_default.reset,
569
+ disabled,
570
+ onClick: () => setDraft((current) => ({
571
+ ...current,
572
+ autoApprove: base.autoApprove ?? true
573
+ })),
574
+ children: t("reset")
575
+ }) : null]
576
+ }),
577
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
578
+ id: "ham-autoApprove",
579
+ type: "button",
580
+ role: "switch",
581
+ "aria-checked": draft.autoApprove ?? true,
582
+ "aria-label": t("autoApprove"),
583
+ className: `${SettingsCard_module_css_default.switch} ${draft.autoApprove ?? true ? SettingsCard_module_css_default.switchOn : ""}`,
584
+ disabled,
585
+ onClick: () => setDraft((current) => ({
586
+ ...current,
587
+ autoApprove: !(current.autoApprove ?? true)
588
+ })),
589
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: SettingsCard_module_css_default.switchThumb })
590
+ }),
591
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
592
+ className: SettingsCard_module_css_default.hint,
593
+ children: t("autoApproveHint")
594
+ })
595
+ ]
596
+ }),
597
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
598
+ className: SettingsCard_module_css_default.field,
599
+ children: [
600
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
601
+ className: SettingsCard_module_css_default.fieldHead,
602
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
603
+ className: SettingsCard_module_css_default.label,
604
+ htmlFor: "ham-shelf",
605
+ children: t("shelf")
606
+ }), draftShelf !== baseShelf ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
607
+ type: "button",
608
+ className: SettingsCard_module_css_default.reset,
609
+ disabled,
610
+ onClick: () => setDraft((current) => ({
611
+ ...current,
612
+ shelf: baseShelf
613
+ })),
614
+ children: t("reset")
615
+ }) : null]
616
+ }),
617
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
618
+ id: "ham-shelf",
619
+ className: SettingsCard_module_css_default.select,
620
+ value: draftShelf,
621
+ disabled: disabled || saving,
622
+ onChange: (event) => {
623
+ const shelf = event.target.value;
624
+ setDraft((current) => ({
625
+ ...current,
626
+ shelf
627
+ }));
628
+ },
629
+ children: choices.map((choice) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
630
+ value: choice.name,
631
+ children: [
632
+ choice.name,
633
+ choice.path !== "" ? ` — ${choice.path}` : "",
634
+ !choice.listed ? t("shelfOptionStatus", { status: t("shelfNotListed") }) : !choice.connected ? t("shelfOptionStatus", { status: t("shelfDisconnected") }) : ""
635
+ ]
636
+ }, choice.name))
637
+ }),
638
+ shelfStatus === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
639
+ className: SettingsCard_module_css_default.notice,
640
+ role: "status",
641
+ children: t("shelfLoading")
642
+ }) : null,
643
+ shelfStatus === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
644
+ className: SettingsCard_module_css_default.catalogError,
645
+ role: "alert",
646
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("shelfLoadFailed") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
647
+ type: "button",
648
+ disabled: saving,
649
+ onClick: () => {
650
+ loadShelves();
651
+ },
652
+ children: t("retry")
653
+ })]
654
+ }) : null,
655
+ shelfStatus === "ready" && shelfListingError !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
656
+ className: SettingsCard_module_css_default.notice,
657
+ role: "status",
658
+ children: t("shelfListingFailed", { message: shelfListingError })
659
+ }) : null,
660
+ shelfStatus === "ready" && shelfListingError === "" && chosen !== void 0 && (!chosen.listed || !chosen.connected) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
661
+ className: SettingsCard_module_css_default.warning,
662
+ role: "status",
663
+ children: t(chosen.listed ? "shelfDisconnectedWarning" : "shelfNotListedWarning", { shelf: chosen.name })
664
+ }) : null,
665
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
666
+ className: SettingsCard_module_css_default.hint,
667
+ children: t("shelfHint")
668
+ })
669
+ ]
670
+ }),
671
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
672
+ className: SettingsCard_module_css_default.group,
673
+ "aria-labelledby": "ham-consolidation-title",
674
+ children: [
675
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
676
+ id: "ham-consolidation-title",
677
+ className: SettingsCard_module_css_default.groupTitle,
678
+ children: t("consolidationTitle")
679
+ }),
680
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
681
+ className: SettingsCard_module_css_default.hint,
682
+ children: t("modelSelectionHint")
683
+ }),
684
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
685
+ className: SettingsCard_module_css_default.modelSelection,
686
+ children: [
687
+ catalogStatus === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
688
+ className: SettingsCard_module_css_default.notice,
689
+ role: "status",
690
+ children: t("modelCatalogLoading")
691
+ }) : null,
692
+ catalogStatus === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
693
+ className: SettingsCard_module_css_default.catalogError,
694
+ role: "alert",
695
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("modelCatalogFailed") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
696
+ type: "button",
697
+ disabled: saving,
698
+ onClick: () => {
699
+ loadCatalog();
700
+ },
701
+ children: t("retry")
702
+ })]
703
+ }) : null,
704
+ catalogPartial ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
705
+ className: SettingsCard_module_css_default.notice,
706
+ children: t("modelCatalogPartial")
707
+ }) : null,
708
+ candidates.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
709
+ className: SettingsCard_module_css_default.models,
710
+ children: [
711
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("legend", { children: t("selectedModels") }),
712
+ [...availableGroups].map(([provider, group]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
713
+ className: SettingsCard_module_css_default.modelGroup,
714
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
715
+ className: SettingsCard_module_css_default.providerName,
716
+ children: group.providerName
717
+ }), group.candidates.map(renderCandidate)]
718
+ }, provider)),
719
+ unavailable.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
720
+ className: SettingsCard_module_css_default.modelGroup,
721
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
722
+ className: SettingsCard_module_css_default.providerName,
723
+ children: t("unavailableModels")
724
+ }), unavailable.map(renderCandidate)]
725
+ }) : null
726
+ ]
727
+ }) : catalogStatus === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
728
+ className: SettingsCard_module_css_default.notice,
729
+ children: t("modelCatalogEmpty")
730
+ }) : null
731
+ ]
732
+ }),
733
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
734
+ className: SettingsCard_module_css_default.pairedFields,
735
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
736
+ className: SettingsCard_module_css_default.field,
737
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
738
+ className: SettingsCard_module_css_default.fieldHead,
739
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
740
+ className: SettingsCard_module_css_default.label,
741
+ htmlFor: "ham-checkEveryTurns",
742
+ children: t("checkEveryTurns")
743
+ }), consolidation.checkEveryTurns !== (base.consolidation?.checkEveryTurns ?? DEFAULT_CONSOLIDATION.checkEveryTurns) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
744
+ type: "button",
745
+ className: SettingsCard_module_css_default.reset,
746
+ disabled,
747
+ onClick: () => updateConsolidation({ checkEveryTurns: base.consolidation?.checkEveryTurns ?? DEFAULT_CONSOLIDATION.checkEveryTurns }),
748
+ children: t("reset")
749
+ }) : null]
750
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
751
+ id: "ham-checkEveryTurns",
752
+ className: SettingsCard_module_css_default.input,
753
+ type: "number",
754
+ min: 1,
755
+ value: consolidation.checkEveryTurns,
756
+ disabled,
757
+ onChange: (event) => updateConsolidation({ checkEveryTurns: Number(event.target.value) })
758
+ })]
759
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
760
+ className: SettingsCard_module_css_default.field,
761
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
762
+ className: SettingsCard_module_css_default.fieldHead,
763
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
764
+ className: SettingsCard_module_css_default.label,
765
+ htmlFor: "ham-minNewTokens",
766
+ children: t("minNewTokens")
767
+ }), consolidation.minNewTokens !== (base.consolidation?.minNewTokens ?? DEFAULT_CONSOLIDATION.minNewTokens) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
768
+ type: "button",
769
+ className: SettingsCard_module_css_default.reset,
770
+ disabled,
771
+ onClick: () => updateConsolidation({ minNewTokens: base.consolidation?.minNewTokens ?? DEFAULT_CONSOLIDATION.minNewTokens }),
772
+ children: t("reset")
773
+ }) : null]
774
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
775
+ id: "ham-minNewTokens",
776
+ className: SettingsCard_module_css_default.input,
777
+ type: "number",
778
+ min: 0,
779
+ value: consolidation.minNewTokens,
780
+ disabled,
781
+ onChange: (event) => updateConsolidation({ minNewTokens: Number(event.target.value) })
782
+ })]
783
+ })]
784
+ }),
785
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
786
+ className: SettingsCard_module_css_default.pairedFields,
787
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
788
+ className: SettingsCard_module_css_default.field,
789
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
790
+ className: SettingsCard_module_css_default.fieldHead,
791
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
792
+ className: SettingsCard_module_css_default.label,
793
+ htmlFor: "ham-cascadeBatchSize",
794
+ children: t("cascadeBatchSize")
795
+ }), (consolidation.cascade?.batchSize ?? DEFAULT_CONSOLIDATION.cascade.batchSize) !== (base.consolidation?.cascade?.batchSize ?? DEFAULT_CONSOLIDATION.cascade.batchSize) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
796
+ type: "button",
797
+ className: SettingsCard_module_css_default.reset,
798
+ disabled,
799
+ onClick: () => updateConsolidation({ cascade: {
800
+ ...consolidation.cascade ?? DEFAULT_CONSOLIDATION.cascade,
801
+ batchSize: base.consolidation?.cascade?.batchSize ?? DEFAULT_CONSOLIDATION.cascade.batchSize
802
+ } }),
803
+ children: t("reset")
804
+ }) : null]
805
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
806
+ id: "ham-cascadeBatchSize",
807
+ className: SettingsCard_module_css_default.input,
808
+ type: "number",
809
+ min: 2,
810
+ value: consolidation.cascade?.batchSize ?? DEFAULT_CONSOLIDATION.cascade.batchSize,
811
+ disabled,
812
+ onChange: (event) => updateConsolidation({ cascade: {
813
+ ...consolidation.cascade ?? DEFAULT_CONSOLIDATION.cascade,
814
+ batchSize: Number(event.target.value)
815
+ } })
816
+ })]
817
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
818
+ className: SettingsCard_module_css_default.field,
819
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
820
+ className: SettingsCard_module_css_default.fieldHead,
821
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
822
+ className: SettingsCard_module_css_default.label,
823
+ htmlFor: "ham-dedupMaxDistance",
824
+ children: t("dedupMaxDistance")
825
+ }), (consolidation.dedupMaxDistance ?? DEFAULT_CONSOLIDATION.dedupMaxDistance) !== (base.consolidation?.dedupMaxDistance ?? DEFAULT_CONSOLIDATION.dedupMaxDistance) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
826
+ type: "button",
827
+ className: SettingsCard_module_css_default.reset,
828
+ disabled,
829
+ onClick: () => updateConsolidation({ dedupMaxDistance: base.consolidation?.dedupMaxDistance ?? DEFAULT_CONSOLIDATION.dedupMaxDistance }),
830
+ children: t("reset")
831
+ }) : null]
832
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
833
+ id: "ham-dedupMaxDistance",
834
+ className: SettingsCard_module_css_default.input,
835
+ type: "number",
836
+ min: 0,
837
+ step: .05,
838
+ value: consolidation.dedupMaxDistance ?? DEFAULT_CONSOLIDATION.dedupMaxDistance,
839
+ disabled,
840
+ onChange: (event) => updateConsolidation({ dedupMaxDistance: Number(event.target.value) })
841
+ })]
842
+ })]
843
+ }),
844
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
845
+ className: SettingsCard_module_css_default.hint,
846
+ children: t("cascadeHint")
847
+ }),
848
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
849
+ className: SettingsCard_module_css_default.limitsTitle,
850
+ children: t("limitsTitle")
851
+ }),
852
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
853
+ className: SettingsCard_module_css_default.hint,
854
+ children: t("limitsHint")
855
+ }),
856
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
857
+ className: SettingsCard_module_css_default.pairedFields,
858
+ children: [
859
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
860
+ className: SettingsCard_module_css_default.field,
861
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
862
+ className: SettingsCard_module_css_default.fieldHead,
863
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
864
+ className: SettingsCard_module_css_default.label,
865
+ htmlFor: "ham-maxInputTokens",
866
+ children: t("maxInputTokens")
867
+ }), consolidation.maxInputTokens !== (base.consolidation?.maxInputTokens ?? DEFAULT_CONSOLIDATION.maxInputTokens) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
868
+ type: "button",
869
+ className: SettingsCard_module_css_default.reset,
870
+ disabled,
871
+ onClick: () => updateConsolidation({ maxInputTokens: base.consolidation?.maxInputTokens ?? DEFAULT_CONSOLIDATION.maxInputTokens }),
872
+ children: t("reset")
873
+ }) : null]
874
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
875
+ id: "ham-maxInputTokens",
876
+ className: SettingsCard_module_css_default.input,
877
+ type: "number",
878
+ min: 1e3,
879
+ step: 1e3,
880
+ value: consolidation.maxInputTokens,
881
+ disabled,
882
+ onChange: (event) => updateConsolidation({ maxInputTokens: Number(event.target.value) })
883
+ })]
884
+ }),
885
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
886
+ className: SettingsCard_module_css_default.field,
887
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
888
+ className: SettingsCard_module_css_default.fieldHead,
889
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
890
+ className: SettingsCard_module_css_default.label,
891
+ htmlFor: "ham-maxOutputTokens",
892
+ children: t("maxOutputTokens")
893
+ }), consolidation.maxOutputTokens !== (base.consolidation?.maxOutputTokens ?? DEFAULT_CONSOLIDATION.maxOutputTokens) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
894
+ type: "button",
895
+ className: SettingsCard_module_css_default.reset,
896
+ disabled,
897
+ onClick: () => updateConsolidation({ maxOutputTokens: base.consolidation?.maxOutputTokens ?? DEFAULT_CONSOLIDATION.maxOutputTokens }),
898
+ children: t("reset")
899
+ }) : null]
900
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
901
+ id: "ham-maxOutputTokens",
902
+ className: SettingsCard_module_css_default.input,
903
+ type: "number",
904
+ min: 200,
905
+ step: 200,
906
+ value: consolidation.maxOutputTokens,
907
+ disabled,
908
+ onChange: (event) => updateConsolidation({ maxOutputTokens: Number(event.target.value) })
909
+ })]
910
+ }),
911
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
912
+ className: SettingsCard_module_css_default.field,
913
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
914
+ className: SettingsCard_module_css_default.fieldHead,
915
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
916
+ className: SettingsCard_module_css_default.label,
917
+ htmlFor: "ham-maxWorkUnitsPerRun",
918
+ children: t("maxWorkUnitsPerRun")
919
+ }), consolidation.maxWorkUnitsPerRun !== (base.consolidation?.maxWorkUnitsPerRun ?? DEFAULT_CONSOLIDATION.maxWorkUnitsPerRun) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
920
+ type: "button",
921
+ className: SettingsCard_module_css_default.reset,
922
+ disabled,
923
+ onClick: () => updateConsolidation({ maxWorkUnitsPerRun: base.consolidation?.maxWorkUnitsPerRun ?? DEFAULT_CONSOLIDATION.maxWorkUnitsPerRun }),
924
+ children: t("reset")
925
+ }) : null]
926
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
927
+ id: "ham-maxWorkUnitsPerRun",
928
+ className: SettingsCard_module_css_default.input,
929
+ type: "number",
930
+ min: 1,
931
+ max: 10,
932
+ value: consolidation.maxWorkUnitsPerRun,
933
+ disabled,
934
+ onChange: (event) => updateConsolidation({ maxWorkUnitsPerRun: Number(event.target.value) })
935
+ })]
936
+ })
937
+ ]
938
+ })
939
+ ]
940
+ }),
941
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
942
+ className: SettingsCard_module_css_default.field,
943
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
944
+ className: SettingsCard_module_css_default.fieldHead,
945
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
946
+ className: SettingsCard_module_css_default.label,
947
+ htmlFor: "ham-preloadRulesTaboos",
948
+ children: t("recallPreload")
949
+ }), (recall.preloadRulesTaboos ?? true) !== (base.recall?.preloadRulesTaboos ?? true) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
950
+ type: "button",
951
+ className: SettingsCard_module_css_default.reset,
952
+ disabled,
953
+ onClick: () => setDraft((current) => ({
954
+ ...current,
955
+ recall: {
956
+ ...current.recall ?? { preloadRulesTaboos: true },
957
+ preloadRulesTaboos: base.recall?.preloadRulesTaboos ?? true
958
+ }
959
+ })),
960
+ children: t("reset")
961
+ }) : null]
962
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
963
+ id: "ham-preloadRulesTaboos",
964
+ type: "button",
965
+ role: "switch",
966
+ "aria-checked": recall.preloadRulesTaboos ?? true,
967
+ "aria-label": t("recallPreload"),
968
+ className: `${SettingsCard_module_css_default.switch} ${recall.preloadRulesTaboos ?? true ? SettingsCard_module_css_default.switchOn : ""}`,
969
+ disabled,
970
+ onClick: () => setDraft((current) => ({
971
+ ...current,
972
+ recall: {
973
+ ...current.recall ?? { preloadRulesTaboos: true },
974
+ preloadRulesTaboos: !(current.recall?.preloadRulesTaboos ?? true)
975
+ }
976
+ })),
977
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: SettingsCard_module_css_default.switchThumb })
978
+ })]
979
+ }),
980
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
981
+ className: SettingsCard_module_css_default.advancedHint,
982
+ children: t("advancedHint", { ns: NS })
983
+ }),
984
+ error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
985
+ className: SettingsCard_module_css_default.error,
986
+ role: "status",
987
+ children: t("saveFailed", { message: error })
988
+ }) : null,
989
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
990
+ className: SettingsCard_module_css_default.footer,
991
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
992
+ variant: "outline",
993
+ size: "sm",
994
+ disabled: !dirty || saving,
995
+ onClick: discard,
996
+ children: t("discard")
997
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
998
+ variant: "primary",
999
+ size: "sm",
1000
+ disabled: saveBlocked,
1001
+ onClick: save,
1002
+ children: saving ? t("saving") : t("save")
1003
+ })]
1004
+ })
1005
+ ]
1006
+ }) : null]
1007
+ });
1008
+ }
1009
+ //#endregion
1010
+ //#region src/client/index.tsx
1011
+ const APPLY_CLAIM = "__dshHypatiaAutoMemoryApplied";
1012
+ function claimApply() {
1013
+ const scope = globalThis;
1014
+ if (scope[APPLY_CLAIM] === true) return false;
1015
+ scope[APPLY_CLAIM] = true;
1016
+ return true;
1017
+ }
1018
+ function releaseApply() {
1019
+ delete globalThis[APPLY_CLAIM];
1020
+ }
1021
+ /**
1022
+ * Required client services: settings, locale, slots, the Host model catalog,
1023
+ * and the settings descriptor. Each Remote namespace is its own service, so
1024
+ * `remote.settings` must be declared here or reading it throws.
1025
+ */
1026
+ const inject = [
1027
+ "slots",
1028
+ "settingsScope",
1029
+ "locale",
1030
+ "remote",
1031
+ "remote.session",
1032
+ "remote.settings"
1033
+ ];
1034
+ /** Mount the browser half. */
1035
+ function apply(ctx) {
1036
+ if (!claimApply()) return;
1037
+ const scope = ctx.get("settingsScope").bind({ namespace: NS });
1038
+ const locale = ctx.get("locale");
1039
+ ctx.effect(() => locale.register(NS, {
1040
+ zh,
1041
+ en
1042
+ }), "dsh-hypatia-auto-memory: locale dictionaries");
1043
+ const loadModelCatalog = async () => {
1044
+ const response = await ctx.remote.session.modelCatalog();
1045
+ if (!response.ok) throw new Error(`${response.error.code}: ${response.error.message}`);
1046
+ return {
1047
+ groups: response.value.groups,
1048
+ partial: response.value.failures.length > 0
1049
+ };
1050
+ };
1051
+ const loadShelfInventory = async () => {
1052
+ const response = await ctx.remote.settings.describe();
1053
+ if (!response.ok) throw new Error(`${response.error.code}: ${response.error.message}`);
1054
+ const inventory = readShelfInventory(response.value.namespaces);
1055
+ if (inventory === void 0) throw new Error("the Host publishes no shelf listing");
1056
+ return inventory;
1057
+ };
1058
+ const renderCard = (props) => (0, react.createElement)(SettingsCard, {
1059
+ scope,
1060
+ loadModelCatalog,
1061
+ loadShelfInventory,
1062
+ ...props
1063
+ });
1064
+ const unregister = ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
1065
+ name: "settings.plugin.item",
1066
+ key: NS,
1067
+ locale: NS
1068
+ }, renderCard));
1069
+ ctx.effect(() => () => {
1070
+ unregister();
1071
+ releaseApply();
1072
+ });
1073
+ }
1074
+ //#endregion
1075
+ exports.apply = apply;
1076
+ exports.inject = inject;
1077
+ return module.exports;
1078
+ }
1079
+ });
1080
+
1081
+ //# sourceMappingURL=client.js.map