dsh-remember 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/LICENSE +21 -0
- package/README.md +111 -0
- package/README.zh-CN.md +111 -0
- package/cordis.patch.yml +5 -0
- package/lib/client.js +1872 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +10434 -0
- package/package.json +61 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,1872 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({ id: "dsh-remember", factory: (require) => {
|
|
2
|
+
var module = { exports: {} }; var exports = module.exports;
|
|
3
|
+
//#region rolldown:runtime
|
|
4
|
+
var __create = Object.create;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
21
|
+
value: mod,
|
|
22
|
+
enumerable: true
|
|
23
|
+
}) : target, mod));
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
let react = require("react");
|
|
27
|
+
react = __toESM(react);
|
|
28
|
+
|
|
29
|
+
//#region src/client/store.ts
|
|
30
|
+
/** Minimal observable snapshot store (useSyncExternalStore-compatible). */
|
|
31
|
+
var SnapshotStore = class {
|
|
32
|
+
snapshot;
|
|
33
|
+
listeners = /* @__PURE__ */ new Set();
|
|
34
|
+
constructor(initial) {
|
|
35
|
+
this.snapshot = initial;
|
|
36
|
+
}
|
|
37
|
+
getSnapshot = () => this.snapshot;
|
|
38
|
+
subscribe = (listener) => {
|
|
39
|
+
this.listeners.add(listener);
|
|
40
|
+
return () => {
|
|
41
|
+
this.listeners.delete(listener);
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
set(next) {
|
|
45
|
+
if (Object.is(next, this.snapshot)) return;
|
|
46
|
+
this.snapshot = next;
|
|
47
|
+
for (const listener of [...this.listeners]) listener();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/client/memory-card.ts
|
|
53
|
+
let pageStyleInjected = false;
|
|
54
|
+
/** Interactive styles (inline styles cannot express :hover/:focus). */
|
|
55
|
+
function ensurePageStyles() {
|
|
56
|
+
if (pageStyleInjected || typeof document === "undefined") return;
|
|
57
|
+
if (document.querySelector("style[data-dsh-memory-page]") != null) {
|
|
58
|
+
pageStyleInjected = true;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const tag = document.createElement("style");
|
|
62
|
+
tag.dataset.dshMemoryPage = "true";
|
|
63
|
+
tag.textContent = [
|
|
64
|
+
"@keyframes dsh-memory-pulse { 0% { opacity: 0.35; transform: scale(0.8); } 50% { opacity: 1; transform: scale(1.08); } 100% { opacity: 0.35; transform: scale(0.8); } }",
|
|
65
|
+
".dshm-btn { transition: border-color 150ms ease, background 150ms ease, color 150ms ease, opacity 150ms ease, transform 100ms ease; }",
|
|
66
|
+
".dshm-btn:hover:not(:disabled) { border-color: var(--dsw-alias-brand-primary, #4b7bec); opacity: 1; background: rgba(75, 123, 236, 0.10); }",
|
|
67
|
+
".dshm-btn:active:not(:disabled) { transform: translateY(1px); }",
|
|
68
|
+
".dshm-btn:focus-visible { outline: 2px solid rgba(75, 123, 236, 0.5); outline-offset: 1px; }",
|
|
69
|
+
".dshm-input { transition: border-color 150ms ease, box-shadow 150ms ease, background 150ms ease; outline: none; }",
|
|
70
|
+
".dshm-input:hover:not(:disabled) { border-color: rgba(128, 128, 128, 0.45); }",
|
|
71
|
+
".dshm-input:focus { border-color: var(--dsw-alias-brand-primary, #4b7bec); box-shadow: 0 0 0 3px rgba(75, 123, 236, 0.18); background: var(--dsw-alias-bg-base, transparent); }",
|
|
72
|
+
".dshm-row + .dshm-row { border-top: 1px solid var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.12)); }",
|
|
73
|
+
".dshm-switch { box-sizing: border-box; position: relative; flex: 0 0 auto; width: 36px; height: 20px; padding: 2px; border: 0; border-radius: 10px; corner-shape: round; background: var(--dsw-alias-border-l3, rgba(128, 128, 128, 0.45)); cursor: pointer; }",
|
|
74
|
+
".dshm-switch[aria-checked=\"true\"] { background: var(--dsw-alias-brand-primary, #4b7bec); }",
|
|
75
|
+
".dshm-switch:disabled { cursor: default; opacity: 0.5; }",
|
|
76
|
+
".dshm-switch:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #4b7bec); outline-offset: 2px; }",
|
|
77
|
+
".dshm-thumb { display: block; width: 16px; height: 16px; border-radius: 50%; corner-shape: round; background: var(--dsw-alias-label-primary-foreground, #fff); transition: transform 120ms ease; }",
|
|
78
|
+
".dshm-switch[aria-checked=\"true\"] .dshm-thumb { transform: translateX(16px); }",
|
|
79
|
+
".dshm-advanced-head { transition: background 120ms ease; border-radius: 8px; }",
|
|
80
|
+
".dshm-advanced-head:hover { background: rgba(128, 128, 128, 0.07); }"
|
|
81
|
+
].join("\n");
|
|
82
|
+
document.head.appendChild(tag);
|
|
83
|
+
pageStyleInjected = true;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Every MemoryPluginConfig field, grouped for display. `advanced` collects the
|
|
87
|
+
* knobs a routine user never touches (paths, backoff/retry tuning, the
|
|
88
|
+
* consolidator denylist) behind a collapsed section.
|
|
89
|
+
*/
|
|
90
|
+
const FIELD_SPECS = [
|
|
91
|
+
{
|
|
92
|
+
field: "enabled",
|
|
93
|
+
kind: "boolean",
|
|
94
|
+
group: "general"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
field: "generateMemories",
|
|
98
|
+
kind: "boolean",
|
|
99
|
+
group: "general"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
field: "useMemories",
|
|
103
|
+
kind: "boolean",
|
|
104
|
+
group: "general"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
field: "extractModel",
|
|
108
|
+
kind: "model",
|
|
109
|
+
group: "models"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
field: "consolidationModel",
|
|
113
|
+
kind: "model",
|
|
114
|
+
group: "models"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
field: "scanIntervalMinutes",
|
|
118
|
+
kind: "number",
|
|
119
|
+
group: "schedule"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
field: "minRolloutIdleHours",
|
|
123
|
+
kind: "number",
|
|
124
|
+
group: "schedule"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
field: "maxSessionAgeDays",
|
|
128
|
+
kind: "number",
|
|
129
|
+
group: "schedule"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
field: "maxUnusedDays",
|
|
133
|
+
kind: "number",
|
|
134
|
+
group: "schedule"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
field: "summaryTokenLimit",
|
|
138
|
+
kind: "number",
|
|
139
|
+
group: "recall"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
field: "redactSecrets",
|
|
143
|
+
kind: "boolean",
|
|
144
|
+
group: "recall"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
field: "workspaceDir",
|
|
148
|
+
kind: "string",
|
|
149
|
+
group: "advanced"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
field: "dbPath",
|
|
153
|
+
kind: "string",
|
|
154
|
+
group: "advanced"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
field: "phase2TimeoutMinutes",
|
|
158
|
+
kind: "number",
|
|
159
|
+
group: "advanced"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
field: "phase2SuccessCooldownSeconds",
|
|
163
|
+
kind: "number",
|
|
164
|
+
group: "advanced"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
field: "phase1RetryBackoffMinutes",
|
|
168
|
+
kind: "number",
|
|
169
|
+
group: "advanced"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
field: "phase1MaxRetries",
|
|
173
|
+
kind: "number",
|
|
174
|
+
group: "advanced"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
field: "phase1MaxPerCycle",
|
|
178
|
+
kind: "number",
|
|
179
|
+
group: "advanced"
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
field: "phase1MaxConcurrency",
|
|
183
|
+
kind: "number",
|
|
184
|
+
group: "advanced"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
field: "extractInputContextRatio",
|
|
188
|
+
kind: "number",
|
|
189
|
+
group: "advanced"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
field: "maxRolloutSummaries",
|
|
193
|
+
kind: "number",
|
|
194
|
+
group: "advanced"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
field: "consolidatorDenyPatterns",
|
|
198
|
+
kind: "list",
|
|
199
|
+
group: "advanced"
|
|
200
|
+
}
|
|
201
|
+
];
|
|
202
|
+
const FIELD_ORDER = new Map(FIELD_SPECS.map((spec, index) => [spec.field, index]));
|
|
203
|
+
/** Debounce between the last edit and its automatic commit. */
|
|
204
|
+
const AUTOSAVE_DELAY_MS = 500;
|
|
205
|
+
function formatValue(spec, value) {
|
|
206
|
+
if (value === void 0 || value === null) return "";
|
|
207
|
+
if (spec.kind === "boolean") return value === true ? "true" : "false";
|
|
208
|
+
if (spec.kind === "list") return Array.isArray(value) ? value.join(", ") : "";
|
|
209
|
+
return String(value);
|
|
210
|
+
}
|
|
211
|
+
function parseValue(spec, text) {
|
|
212
|
+
const trimmed = text.trim();
|
|
213
|
+
if (spec.kind === "boolean") {
|
|
214
|
+
if (trimmed === "true") return {
|
|
215
|
+
ok: true,
|
|
216
|
+
value: true
|
|
217
|
+
};
|
|
218
|
+
if (trimmed === "false") return {
|
|
219
|
+
ok: true,
|
|
220
|
+
value: false
|
|
221
|
+
};
|
|
222
|
+
return { ok: false };
|
|
223
|
+
}
|
|
224
|
+
if (trimmed === "") return {
|
|
225
|
+
ok: true,
|
|
226
|
+
value: void 0
|
|
227
|
+
};
|
|
228
|
+
if (spec.kind === "number") {
|
|
229
|
+
const parsed = Number(trimmed);
|
|
230
|
+
return Number.isFinite(parsed) ? {
|
|
231
|
+
ok: true,
|
|
232
|
+
value: parsed
|
|
233
|
+
} : { ok: false };
|
|
234
|
+
}
|
|
235
|
+
if (spec.kind === "list") {
|
|
236
|
+
const items = trimmed.split(",").map((item) => item.trim()).filter((item) => item !== "");
|
|
237
|
+
return items.length > 0 ? {
|
|
238
|
+
ok: true,
|
|
239
|
+
value: items
|
|
240
|
+
} : {
|
|
241
|
+
ok: true,
|
|
242
|
+
value: void 0
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
ok: true,
|
|
247
|
+
value: trimmed
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
var MemoryCardController = class {
|
|
251
|
+
staged = /* @__PURE__ */ new Map();
|
|
252
|
+
store;
|
|
253
|
+
saving = false;
|
|
254
|
+
failed = false;
|
|
255
|
+
autosaveTimer = null;
|
|
256
|
+
catalogStatus;
|
|
257
|
+
catalogModels = [];
|
|
258
|
+
constructor(scope, loadCatalog) {
|
|
259
|
+
this.scope = scope;
|
|
260
|
+
this.loadCatalog = loadCatalog;
|
|
261
|
+
this.catalogStatus = loadCatalog == null ? "unavailable" : "loading";
|
|
262
|
+
this.store = new SnapshotStore(this.project());
|
|
263
|
+
scope.subscribe(() => {
|
|
264
|
+
this.store.set(this.project());
|
|
265
|
+
});
|
|
266
|
+
if (loadCatalog != null) this.refreshCatalog();
|
|
267
|
+
}
|
|
268
|
+
/** Re-query the Host model catalog (exposed as a page action). */
|
|
269
|
+
async refreshCatalog() {
|
|
270
|
+
if (this.loadCatalog == null) return;
|
|
271
|
+
this.catalogStatus = "loading";
|
|
272
|
+
this.publish();
|
|
273
|
+
try {
|
|
274
|
+
this.catalogModels = await this.loadCatalog();
|
|
275
|
+
this.catalogStatus = "ready";
|
|
276
|
+
} catch {
|
|
277
|
+
this.catalogStatus = "error";
|
|
278
|
+
}
|
|
279
|
+
this.publish();
|
|
280
|
+
}
|
|
281
|
+
/** The observable page snapshot source for the section's hooks seat. */
|
|
282
|
+
pageStore() {
|
|
283
|
+
return this.store;
|
|
284
|
+
}
|
|
285
|
+
inject() {
|
|
286
|
+
return {
|
|
287
|
+
hooks: { memoryPage: this.store },
|
|
288
|
+
edit: (field, text) => {
|
|
289
|
+
this.staged.set(field, {
|
|
290
|
+
text,
|
|
291
|
+
clear: false
|
|
292
|
+
});
|
|
293
|
+
this.failed = false;
|
|
294
|
+
this.publish();
|
|
295
|
+
this.scheduleAutosave();
|
|
296
|
+
},
|
|
297
|
+
resetField: (field) => {
|
|
298
|
+
const spec = this.specOf(field);
|
|
299
|
+
const base = this.scope.getSnapshot().base?.[field];
|
|
300
|
+
this.staged.set(field, {
|
|
301
|
+
text: formatValue(spec, base),
|
|
302
|
+
clear: true
|
|
303
|
+
});
|
|
304
|
+
this.failed = false;
|
|
305
|
+
this.publish();
|
|
306
|
+
this.scheduleAutosave();
|
|
307
|
+
},
|
|
308
|
+
refreshCatalog: () => {
|
|
309
|
+
this.refreshCatalog();
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
/** Auto-commit pending edits once typing pauses (checkbox/select/reset included). */
|
|
314
|
+
scheduleAutosave() {
|
|
315
|
+
if (this.autosaveTimer != null) clearTimeout(this.autosaveTimer);
|
|
316
|
+
this.autosaveTimer = setTimeout(() => {
|
|
317
|
+
this.autosaveTimer = null;
|
|
318
|
+
this.save();
|
|
319
|
+
}, AUTOSAVE_DELAY_MS);
|
|
320
|
+
}
|
|
321
|
+
specOf(field) {
|
|
322
|
+
const spec = FIELD_SPECS.find((candidate) => candidate.field === field);
|
|
323
|
+
if (spec == null) throw new Error(`memory page has no field ${field}`);
|
|
324
|
+
return spec;
|
|
325
|
+
}
|
|
326
|
+
project() {
|
|
327
|
+
const snapshot = this.scope.getSnapshot();
|
|
328
|
+
const section = snapshot.value ?? {};
|
|
329
|
+
const user = snapshot.user;
|
|
330
|
+
const fields = [...FIELD_SPECS].sort((a, b) => (FIELD_ORDER.get(a.field) ?? 0) - (FIELD_ORDER.get(b.field) ?? 0)).map((spec) => {
|
|
331
|
+
const staged = this.staged.get(spec.field);
|
|
332
|
+
if (staged === void 0) return {
|
|
333
|
+
spec,
|
|
334
|
+
text: formatValue(spec, section[spec.field]),
|
|
335
|
+
overridden: user !== void 0 && Object.hasOwn(user, spec.field),
|
|
336
|
+
invalid: false
|
|
337
|
+
};
|
|
338
|
+
const parsed = parseValue(spec, staged.text);
|
|
339
|
+
return {
|
|
340
|
+
spec,
|
|
341
|
+
text: staged.text,
|
|
342
|
+
overridden: staged.clear ? false : parsed.ok && parsed.value !== void 0,
|
|
343
|
+
invalid: !staged.clear && !parsed.ok
|
|
344
|
+
};
|
|
345
|
+
});
|
|
346
|
+
return {
|
|
347
|
+
available: snapshot.status === "ready",
|
|
348
|
+
writable: snapshot.writable,
|
|
349
|
+
dirty: this.plan(fields).length > 0,
|
|
350
|
+
invalid: fields.some((field) => field.invalid),
|
|
351
|
+
saving: this.saving,
|
|
352
|
+
failed: this.failed,
|
|
353
|
+
catalogStatus: this.catalogStatus,
|
|
354
|
+
catalogModels: this.catalogModels,
|
|
355
|
+
fields
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/** Staged edits a save would perform, in field order. */
|
|
359
|
+
plan(fields) {
|
|
360
|
+
const section = this.scope.getSnapshot().value ?? {};
|
|
361
|
+
const plan = [];
|
|
362
|
+
for (const field of fields) {
|
|
363
|
+
const staged = this.staged.get(field.spec.field);
|
|
364
|
+
if (staged === void 0) continue;
|
|
365
|
+
if (staged.clear) {
|
|
366
|
+
if (field.overridden) plan.push({
|
|
367
|
+
field: field.spec.field,
|
|
368
|
+
clear: true
|
|
369
|
+
});
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
const parsed = parseValue(field.spec, staged.text);
|
|
373
|
+
if (!parsed.ok) continue;
|
|
374
|
+
if (formatValue(field.spec, section[field.spec.field]) === staged.text.trim()) continue;
|
|
375
|
+
if (parsed.value === void 0) plan.push({
|
|
376
|
+
field: field.spec.field,
|
|
377
|
+
clear: true
|
|
378
|
+
});
|
|
379
|
+
else plan.push({
|
|
380
|
+
field: field.spec.field,
|
|
381
|
+
clear: false,
|
|
382
|
+
value: parsed.value
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
return plan;
|
|
386
|
+
}
|
|
387
|
+
async save() {
|
|
388
|
+
const fields = this.project().fields;
|
|
389
|
+
const plan = this.plan(fields);
|
|
390
|
+
if (plan.length === 0 || plan.some((item) => !item.clear && item.value === void 0)) return;
|
|
391
|
+
if (this.saving) {
|
|
392
|
+
this.scheduleAutosave();
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
this.saving = true;
|
|
396
|
+
this.failed = false;
|
|
397
|
+
this.publish();
|
|
398
|
+
let landed = true;
|
|
399
|
+
for (const item of plan) try {
|
|
400
|
+
if (item.clear) await this.scope.unset(item.field);
|
|
401
|
+
else await this.scope.set(item.field, item.value);
|
|
402
|
+
} catch {
|
|
403
|
+
landed = false;
|
|
404
|
+
}
|
|
405
|
+
if (landed) for (const item of plan) this.staged.delete(item.field);
|
|
406
|
+
this.saving = false;
|
|
407
|
+
this.failed = !landed;
|
|
408
|
+
this.publish();
|
|
409
|
+
}
|
|
410
|
+
publish() {
|
|
411
|
+
this.store.set(this.project());
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
const ACCENT = "var(--dsw-alias-brand-primary, #4b7bec)";
|
|
415
|
+
const BORDER$1 = "var(--dsw-alias-border-l2, rgba(128, 128, 128, 0.2))";
|
|
416
|
+
const DANGER = "#e05561";
|
|
417
|
+
const MUTED = "var(--dsw-alias-label-secondary, rgba(128, 128, 128, 0.9))";
|
|
418
|
+
function sectionStyle() {
|
|
419
|
+
return { marginTop: 26 };
|
|
420
|
+
}
|
|
421
|
+
function sectionHeaderStyle() {
|
|
422
|
+
return {
|
|
423
|
+
fontSize: 11,
|
|
424
|
+
fontWeight: 650,
|
|
425
|
+
letterSpacing: 1.1,
|
|
426
|
+
textTransform: "uppercase",
|
|
427
|
+
opacity: .5,
|
|
428
|
+
margin: "0 2px 2px"
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
function rowStyle() {
|
|
432
|
+
return {
|
|
433
|
+
display: "flex",
|
|
434
|
+
alignItems: "center",
|
|
435
|
+
justifyContent: "space-between",
|
|
436
|
+
gap: 20,
|
|
437
|
+
minHeight: 34,
|
|
438
|
+
padding: "9px 2px"
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
function labelStackStyle() {
|
|
442
|
+
return {
|
|
443
|
+
display: "flex",
|
|
444
|
+
flexDirection: "column",
|
|
445
|
+
gap: 2,
|
|
446
|
+
minWidth: 0
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
function inputStyle(invalid) {
|
|
450
|
+
return {
|
|
451
|
+
width: 230,
|
|
452
|
+
boxSizing: "border-box",
|
|
453
|
+
padding: "6px 10px",
|
|
454
|
+
fontSize: 12.5,
|
|
455
|
+
borderRadius: 8,
|
|
456
|
+
border: `1px solid ${invalid ? DANGER : BORDER$1}`,
|
|
457
|
+
background: "var(--dsw-alias-fill-tertiary, rgba(128, 128, 128, 0.06))",
|
|
458
|
+
color: "inherit",
|
|
459
|
+
textAlign: "left"
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function ghostButtonStyle(disabled) {
|
|
463
|
+
return {
|
|
464
|
+
fontSize: 12,
|
|
465
|
+
padding: "4px 12px",
|
|
466
|
+
borderRadius: 8,
|
|
467
|
+
cursor: disabled ? "default" : "pointer",
|
|
468
|
+
border: `1px solid ${BORDER$1}`,
|
|
469
|
+
background: "transparent",
|
|
470
|
+
color: "inherit",
|
|
471
|
+
opacity: disabled ? .4 : .8
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function primaryButtonStyle(disabled) {
|
|
475
|
+
return {
|
|
476
|
+
fontSize: 12,
|
|
477
|
+
padding: "4px 14px",
|
|
478
|
+
borderRadius: 8,
|
|
479
|
+
cursor: disabled ? "default" : "pointer",
|
|
480
|
+
border: "1px solid transparent",
|
|
481
|
+
background: disabled ? "transparent" : "var(--dsw-alias-button-info-fill, #4d6bfe)",
|
|
482
|
+
color: disabled ? "inherit" : "var(--dsw-alias-label-primary-inverted, #fff)",
|
|
483
|
+
fontWeight: 600,
|
|
484
|
+
opacity: disabled ? .4 : 1
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function badgeStyle() {
|
|
488
|
+
return {
|
|
489
|
+
fontSize: 10,
|
|
490
|
+
padding: "1px 7px",
|
|
491
|
+
borderRadius: 999,
|
|
492
|
+
background: "var(--dsw-alias-state-business-tertiary, rgba(75, 123, 236, 0.14))",
|
|
493
|
+
color: ACCENT,
|
|
494
|
+
whiteSpace: "nowrap"
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
/** Toggle switch for boolean fields (replaces the bare checkbox). */
|
|
498
|
+
function Switch(props) {
|
|
499
|
+
return (0, react.createElement)("button", {
|
|
500
|
+
type: "button",
|
|
501
|
+
role: "switch",
|
|
502
|
+
"aria-checked": props.on,
|
|
503
|
+
"aria-label": props.label,
|
|
504
|
+
disabled: props.disabled,
|
|
505
|
+
className: "dshm-switch",
|
|
506
|
+
onClick: () => props.onToggle(!props.on)
|
|
507
|
+
}, (0, react.createElement)("span", { className: "dshm-thumb" }));
|
|
508
|
+
}
|
|
509
|
+
function FieldRow(props) {
|
|
510
|
+
const { t, state, disabled } = props;
|
|
511
|
+
const hint = hintText(t, state.spec.field);
|
|
512
|
+
let control;
|
|
513
|
+
if (state.spec.kind === "boolean") control = (0, react.createElement)(Switch, {
|
|
514
|
+
on: state.text === "true",
|
|
515
|
+
disabled,
|
|
516
|
+
label: t(`field_${state.spec.field}`),
|
|
517
|
+
onToggle: (next) => props.onEdit(next ? "true" : "false")
|
|
518
|
+
});
|
|
519
|
+
else if (state.spec.kind === "model") control = (0, react.createElement)(ModelSelect, {
|
|
520
|
+
t,
|
|
521
|
+
state,
|
|
522
|
+
catalogModels: props.catalogModels ?? [],
|
|
523
|
+
disabled,
|
|
524
|
+
onEdit: props.onEdit
|
|
525
|
+
});
|
|
526
|
+
else control = (0, react.createElement)("input", {
|
|
527
|
+
type: "text",
|
|
528
|
+
value: state.text,
|
|
529
|
+
disabled,
|
|
530
|
+
spellCheck: false,
|
|
531
|
+
className: "dshm-input",
|
|
532
|
+
onChange: (event) => {
|
|
533
|
+
props.onEdit(event.target.value);
|
|
534
|
+
},
|
|
535
|
+
style: inputStyle(state.invalid)
|
|
536
|
+
});
|
|
537
|
+
return (0, react.createElement)("div", {
|
|
538
|
+
key: state.spec.field,
|
|
539
|
+
className: "dshm-row",
|
|
540
|
+
style: rowStyle()
|
|
541
|
+
}, (0, react.createElement)("div", { style: labelStackStyle() }, (0, react.createElement)("span", { style: { fontSize: 13 } }, fieldLabel(t, state.spec.field)), hint !== "" ? (0, react.createElement)("span", { style: {
|
|
542
|
+
fontSize: 11.5,
|
|
543
|
+
opacity: .5
|
|
544
|
+
} }, hint) : null), (0, react.createElement)("div", { style: {
|
|
545
|
+
display: "flex",
|
|
546
|
+
alignItems: "center",
|
|
547
|
+
gap: 8,
|
|
548
|
+
flexShrink: 0
|
|
549
|
+
} }, state.overridden ? (0, react.createElement)("span", { style: badgeStyle() }, t("overridden")) : null, state.overridden ? (0, react.createElement)("button", {
|
|
550
|
+
type: "button",
|
|
551
|
+
disabled,
|
|
552
|
+
className: "dshm-btn",
|
|
553
|
+
onClick: props.onReset,
|
|
554
|
+
style: ghostButtonStyle(disabled)
|
|
555
|
+
}, t("reset")) : null, control));
|
|
556
|
+
}
|
|
557
|
+
/** Dropdown over the Host model catalog; empty value = deployment default. */
|
|
558
|
+
function ModelSelect(props) {
|
|
559
|
+
const { t, state, catalogModels } = props;
|
|
560
|
+
const current = state.text;
|
|
561
|
+
const options = [(0, react.createElement)("option", {
|
|
562
|
+
key: "__default__",
|
|
563
|
+
value: ""
|
|
564
|
+
}, t("modelDefault"))];
|
|
565
|
+
for (const model of catalogModels) {
|
|
566
|
+
const value = `${model.provider}/${model.model}`;
|
|
567
|
+
options.push((0, react.createElement)("option", {
|
|
568
|
+
key: value,
|
|
569
|
+
value
|
|
570
|
+
}, `${model.modelName} · ${model.providerName}`));
|
|
571
|
+
}
|
|
572
|
+
if (current !== "" && !catalogModels.some((model) => `${model.provider}/${model.model}` === current)) options.push((0, react.createElement)("option", {
|
|
573
|
+
key: current,
|
|
574
|
+
value: current
|
|
575
|
+
}, current));
|
|
576
|
+
return (0, react.createElement)("select", {
|
|
577
|
+
value: current,
|
|
578
|
+
disabled: props.disabled,
|
|
579
|
+
className: "dshm-input",
|
|
580
|
+
onChange: (event) => {
|
|
581
|
+
props.onEdit(event.target.value);
|
|
582
|
+
},
|
|
583
|
+
style: inputStyle(false)
|
|
584
|
+
}, options);
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Import/export entry: the host-side transfer runs through the memory-commands
|
|
588
|
+
* channel (path field + action), and the outcome comes back as a `transfer:`-
|
|
589
|
+
* prefixed status detail line.
|
|
590
|
+
*/
|
|
591
|
+
function TransferSection(props) {
|
|
592
|
+
const { t, commands } = props;
|
|
593
|
+
const [exportPath, setExportPath] = (0, react.useState)("");
|
|
594
|
+
const [importPath, setImportPath] = (0, react.useState)("");
|
|
595
|
+
const [requested, setRequested] = (0, react.useState)(null);
|
|
596
|
+
const send = (action, file) => {
|
|
597
|
+
if (commands == null || file.trim() === "") return;
|
|
598
|
+
setRequested(action);
|
|
599
|
+
(async () => {
|
|
600
|
+
await commands.set("path", file.trim());
|
|
601
|
+
await commands.set("action", action);
|
|
602
|
+
await commands.set("requestedAt", Date.now());
|
|
603
|
+
})();
|
|
604
|
+
};
|
|
605
|
+
const feedback = props.statusDetail?.startsWith("transfer:") === true ? props.statusDetail.slice(9).trim() : null;
|
|
606
|
+
const feedbackIsError = feedback != null && /fail|need|must|unsupported|error/iu.test(feedback);
|
|
607
|
+
const transferRow = (value, setValue, labelKey, action) => {
|
|
608
|
+
const empty = value.trim() === "";
|
|
609
|
+
return (0, react.createElement)("div", {
|
|
610
|
+
className: "dshm-row",
|
|
611
|
+
style: rowStyle()
|
|
612
|
+
}, (0, react.createElement)("div", { style: labelStackStyle() }, (0, react.createElement)("span", { style: { fontSize: 13 } }, t(labelKey))), (0, react.createElement)("div", { style: {
|
|
613
|
+
display: "flex",
|
|
614
|
+
alignItems: "center",
|
|
615
|
+
gap: 8,
|
|
616
|
+
flexShrink: 0
|
|
617
|
+
} }, (0, react.createElement)("input", {
|
|
618
|
+
type: "text",
|
|
619
|
+
value,
|
|
620
|
+
spellCheck: false,
|
|
621
|
+
className: "dshm-input",
|
|
622
|
+
placeholder: "C:\\…\\memory.dshmem.json",
|
|
623
|
+
onChange: (event) => {
|
|
624
|
+
setValue(event.target.value);
|
|
625
|
+
setRequested(null);
|
|
626
|
+
},
|
|
627
|
+
style: inputStyle(false)
|
|
628
|
+
}), (0, react.createElement)("button", {
|
|
629
|
+
type: "button",
|
|
630
|
+
disabled: commands == null || empty,
|
|
631
|
+
className: "dshm-btn",
|
|
632
|
+
onClick: () => send(action, value),
|
|
633
|
+
style: primaryButtonStyle(commands == null || empty)
|
|
634
|
+
}, t(`transfer_${action}`))));
|
|
635
|
+
};
|
|
636
|
+
return (0, react.createElement)("section", { style: sectionStyle() }, (0, react.createElement)("h3", { style: sectionHeaderStyle() }, t("group_transfer")), (0, react.createElement)("div", { style: { borderTop: `1px solid ${BORDER$1}` } }, transferRow(exportPath, setExportPath, "transfer_exportPath", "export"), transferRow(importPath, setImportPath, "transfer_importPath", "import")), (0, react.createElement)("p", { style: {
|
|
637
|
+
fontSize: 11.5,
|
|
638
|
+
opacity: .5,
|
|
639
|
+
margin: "8px 2px 0",
|
|
640
|
+
lineHeight: 1.5
|
|
641
|
+
} }, t("transfer_hint")), (0, react.createElement)("div", { style: {
|
|
642
|
+
minHeight: 18,
|
|
643
|
+
margin: "4px 2px 0"
|
|
644
|
+
} }, feedback != null ? (0, react.createElement)("span", { style: {
|
|
645
|
+
fontSize: 12,
|
|
646
|
+
color: feedbackIsError ? DANGER : "inherit",
|
|
647
|
+
opacity: feedbackIsError ? 1 : .8
|
|
648
|
+
} }, feedback) : requested != null ? (0, react.createElement)("span", { style: {
|
|
649
|
+
fontSize: 12,
|
|
650
|
+
opacity: .55
|
|
651
|
+
} }, t("transfer_requested")) : null));
|
|
652
|
+
}
|
|
653
|
+
/** Collapsible section for the expert knobs (paths, retry tuning, denylist). */
|
|
654
|
+
function AdvancedSection(props) {
|
|
655
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
656
|
+
const { t } = props;
|
|
657
|
+
return (0, react.createElement)("section", { style: sectionStyle() }, (0, react.createElement)("button", {
|
|
658
|
+
type: "button",
|
|
659
|
+
className: "dshm-advanced-head",
|
|
660
|
+
onClick: () => setOpen(!open),
|
|
661
|
+
style: {
|
|
662
|
+
display: "flex",
|
|
663
|
+
alignItems: "center",
|
|
664
|
+
gap: 6,
|
|
665
|
+
width: "100%",
|
|
666
|
+
padding: "4px 6px 4px 2px",
|
|
667
|
+
border: "none",
|
|
668
|
+
background: "transparent",
|
|
669
|
+
color: "inherit",
|
|
670
|
+
cursor: "pointer",
|
|
671
|
+
textAlign: "left"
|
|
672
|
+
}
|
|
673
|
+
}, (0, react.createElement)("span", { style: {
|
|
674
|
+
fontSize: 9,
|
|
675
|
+
opacity: .55,
|
|
676
|
+
display: "inline-block",
|
|
677
|
+
transform: open ? "rotate(90deg)" : "none",
|
|
678
|
+
transition: "transform 150ms ease"
|
|
679
|
+
} }, "▶"), (0, react.createElement)("span", { style: {
|
|
680
|
+
...sectionHeaderStyle(),
|
|
681
|
+
margin: 0
|
|
682
|
+
} }, t("group_advanced")), !open ? (0, react.createElement)("span", { style: {
|
|
683
|
+
fontSize: 11,
|
|
684
|
+
opacity: .4
|
|
685
|
+
} }, `${props.fields.length}`) : null), open ? (0, react.createElement)("div", { style: { borderTop: `1px solid ${BORDER$1}` } }, ...props.fields.map((field) => (0, react.createElement)(FieldRow, {
|
|
686
|
+
key: field.spec.field,
|
|
687
|
+
t,
|
|
688
|
+
state: field,
|
|
689
|
+
disabled: props.disabled,
|
|
690
|
+
onEdit: (text) => props.onEdit(field.spec.field, text),
|
|
691
|
+
onReset: () => props.onReset(field.spec.field)
|
|
692
|
+
}))) : null);
|
|
693
|
+
}
|
|
694
|
+
function fieldLabel(t, field) {
|
|
695
|
+
return t(`field_${field}`);
|
|
696
|
+
}
|
|
697
|
+
function hintText(t, field) {
|
|
698
|
+
const hint = t(`hint_${field}`);
|
|
699
|
+
return hint === `hint_${field}` ? "" : hint;
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* The dedicated Memory settings page (occupies one `settings.section` seat):
|
|
703
|
+
* live status banner, divided-list sections with catalog-backed model pickers
|
|
704
|
+
* and toggle switches, transfer actions, and a collapsed advanced section.
|
|
705
|
+
* Render failures surface inline instead of blanking the panel.
|
|
706
|
+
*/
|
|
707
|
+
function MemorySettingsPage(props) {
|
|
708
|
+
try {
|
|
709
|
+
return renderMemoryPage(props);
|
|
710
|
+
} catch (error) {
|
|
711
|
+
return (0, react.createElement)("div", { style: {
|
|
712
|
+
fontSize: 12.5,
|
|
713
|
+
color: DANGER,
|
|
714
|
+
maxWidth: 640,
|
|
715
|
+
whiteSpace: "pre-wrap"
|
|
716
|
+
} }, `memory settings page error: ${String(error)}`);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
function renderMemoryPage(props) {
|
|
720
|
+
if (props.useMemoryPage == null) return (0, react.createElement)("div", { style: {
|
|
721
|
+
fontSize: 13,
|
|
722
|
+
opacity: .6
|
|
723
|
+
} }, "renderer did not bind the memory page hook");
|
|
724
|
+
ensurePageStyles();
|
|
725
|
+
const t = props.t;
|
|
726
|
+
const state = props.useMemoryPage((value) => value);
|
|
727
|
+
const statusSnapshot = props.useMemoryStatus != null ? props.useMemoryStatus((value) => value) : void 0;
|
|
728
|
+
if (!state.available) return (0, react.createElement)("div", { style: {
|
|
729
|
+
fontSize: 13,
|
|
730
|
+
opacity: .6
|
|
731
|
+
} }, t("unavailable"));
|
|
732
|
+
const disabled = !state.writable;
|
|
733
|
+
const status = statusSnapshot?.status === "ready" ? statusSnapshot.value : void 0;
|
|
734
|
+
const active = status?.phase === "phase1" || status?.phase === "phase2";
|
|
735
|
+
const statusLabel = active ? status?.phase === "phase1" ? t("phase1") : t("phase2") : t("statusIdle");
|
|
736
|
+
const groups = /* @__PURE__ */ new Map();
|
|
737
|
+
for (const field of state.fields) {
|
|
738
|
+
const list = groups.get(field.spec.group) ?? [];
|
|
739
|
+
list.push(field);
|
|
740
|
+
groups.set(field.spec.group, list);
|
|
741
|
+
}
|
|
742
|
+
const fieldSection = (group) => {
|
|
743
|
+
const fields = groups.get(group);
|
|
744
|
+
if (fields == null || fields.length === 0) return null;
|
|
745
|
+
return (0, react.createElement)("section", {
|
|
746
|
+
key: group,
|
|
747
|
+
style: sectionStyle()
|
|
748
|
+
}, (0, react.createElement)("div", { style: {
|
|
749
|
+
display: "flex",
|
|
750
|
+
alignItems: "center",
|
|
751
|
+
justifyContent: "space-between",
|
|
752
|
+
margin: "0 2px 2px"
|
|
753
|
+
} }, (0, react.createElement)("h3", { style: {
|
|
754
|
+
...sectionHeaderStyle(),
|
|
755
|
+
margin: 0
|
|
756
|
+
} }, t(`group_${group}`)), group === "models" ? (0, react.createElement)("button", {
|
|
757
|
+
type: "button",
|
|
758
|
+
className: "dshm-btn",
|
|
759
|
+
onClick: props.refreshCatalog,
|
|
760
|
+
style: {
|
|
761
|
+
...ghostButtonStyle(false),
|
|
762
|
+
border: "none",
|
|
763
|
+
padding: "2px 6px",
|
|
764
|
+
fontSize: 11.5
|
|
765
|
+
}
|
|
766
|
+
}, state.catalogStatus === "loading" ? t("catalogLoading") : t("catalogRefresh")) : null), group === "models" && state.catalogStatus === "error" ? (0, react.createElement)("div", { style: {
|
|
767
|
+
fontSize: 11.5,
|
|
768
|
+
color: DANGER,
|
|
769
|
+
margin: "0 2px 4px"
|
|
770
|
+
} }, t("catalogError")) : null, (0, react.createElement)("div", { style: { borderTop: `1px solid ${BORDER$1}` } }, ...fields.map((field) => (0, react.createElement)(FieldRow, {
|
|
771
|
+
key: field.spec.field,
|
|
772
|
+
t,
|
|
773
|
+
state: field,
|
|
774
|
+
disabled,
|
|
775
|
+
catalogModels: state.catalogModels,
|
|
776
|
+
onEdit: (text) => props.edit(field.spec.field, text),
|
|
777
|
+
onReset: () => props.resetField(field.spec.field)
|
|
778
|
+
}))));
|
|
779
|
+
};
|
|
780
|
+
return (0, react.createElement)("div", { style: {
|
|
781
|
+
maxWidth: 640,
|
|
782
|
+
paddingBottom: 32
|
|
783
|
+
} }, (0, react.createElement)("h2", { style: {
|
|
784
|
+
fontSize: 19,
|
|
785
|
+
fontWeight: 650,
|
|
786
|
+
margin: "0 0 4px",
|
|
787
|
+
letterSpacing: .2
|
|
788
|
+
} }, t("title")), (0, react.createElement)("p", { style: {
|
|
789
|
+
fontSize: 13,
|
|
790
|
+
opacity: .6,
|
|
791
|
+
margin: "0 0 18px",
|
|
792
|
+
lineHeight: 1.5
|
|
793
|
+
} }, t("intro")), (0, react.createElement)("div", { style: {
|
|
794
|
+
border: `1px solid ${active ? ACCENT : BORDER$1}`,
|
|
795
|
+
borderRadius: 10,
|
|
796
|
+
padding: "10px 14px",
|
|
797
|
+
display: "flex",
|
|
798
|
+
alignItems: "center",
|
|
799
|
+
gap: 10,
|
|
800
|
+
background: active ? "rgba(75, 123, 236, 0.07)" : "transparent",
|
|
801
|
+
transition: "border-color 200ms ease, background 200ms ease"
|
|
802
|
+
} }, (0, react.createElement)("span", { style: {
|
|
803
|
+
width: 8,
|
|
804
|
+
height: 8,
|
|
805
|
+
borderRadius: "50%",
|
|
806
|
+
flexShrink: 0,
|
|
807
|
+
background: active ? ACCENT : MUTED,
|
|
808
|
+
animation: active ? "dsh-memory-pulse 1.4s ease-in-out infinite" : void 0
|
|
809
|
+
} }), (0, react.createElement)("span", { style: {
|
|
810
|
+
fontSize: 12.5,
|
|
811
|
+
fontWeight: 600,
|
|
812
|
+
flexShrink: 0
|
|
813
|
+
} }, statusLabel), status?.detail ? (0, react.createElement)("span", { style: {
|
|
814
|
+
fontSize: 12,
|
|
815
|
+
opacity: .6,
|
|
816
|
+
overflow: "hidden",
|
|
817
|
+
textOverflow: "ellipsis",
|
|
818
|
+
whiteSpace: "nowrap"
|
|
819
|
+
} }, status.detail) : null, (0, react.createElement)("span", { style: {
|
|
820
|
+
fontSize: 11,
|
|
821
|
+
opacity: .4,
|
|
822
|
+
marginLeft: "auto",
|
|
823
|
+
flexShrink: 0
|
|
824
|
+
} }, status?.updatedAt ? new Date(status.updatedAt).toLocaleTimeString() : "")), fieldSection("general"), fieldSection("models"), fieldSection("schedule"), fieldSection("recall"), (0, react.createElement)(TransferSection, {
|
|
825
|
+
t,
|
|
826
|
+
commands: props.commands,
|
|
827
|
+
statusDetail: status?.detail
|
|
828
|
+
}), (0, react.createElement)(AdvancedSection, {
|
|
829
|
+
t,
|
|
830
|
+
fields: groups.get("advanced") ?? [],
|
|
831
|
+
disabled,
|
|
832
|
+
onEdit: (field, text) => props.edit(field, text),
|
|
833
|
+
onReset: (field) => props.resetField(field)
|
|
834
|
+
}), (0, react.createElement)("div", { style: {
|
|
835
|
+
display: "flex",
|
|
836
|
+
alignItems: "center",
|
|
837
|
+
gap: 10,
|
|
838
|
+
marginTop: 14,
|
|
839
|
+
minHeight: 22
|
|
840
|
+
} }, state.saving ? (0, react.createElement)("span", { style: {
|
|
841
|
+
fontSize: 12,
|
|
842
|
+
opacity: .6
|
|
843
|
+
} }, t("saving")) : null, state.failed ? (0, react.createElement)("span", { style: {
|
|
844
|
+
fontSize: 12,
|
|
845
|
+
color: DANGER
|
|
846
|
+
} }, t("failed")) : null, state.invalid ? (0, react.createElement)("span", { style: {
|
|
847
|
+
fontSize: 12,
|
|
848
|
+
color: DANGER
|
|
849
|
+
} }, t("invalid")) : null, !state.writable ? (0, react.createElement)("span", { style: {
|
|
850
|
+
fontSize: 12,
|
|
851
|
+
opacity: .6
|
|
852
|
+
} }, t("readOnly")) : null));
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
//#endregion
|
|
856
|
+
//#region src/client/sidebar-action.ts
|
|
857
|
+
let styleInjected$1 = false;
|
|
858
|
+
/** Inject the widget stylesheet once per document (factory-execution side effect). */
|
|
859
|
+
function ensureStyles() {
|
|
860
|
+
if (styleInjected$1 || typeof document === "undefined") return;
|
|
861
|
+
if (document.querySelector("style[data-dsh-memory-float]") != null) {
|
|
862
|
+
styleInjected$1 = true;
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
const tag = document.createElement("style");
|
|
866
|
+
tag.dataset.dshMemoryFloat = "true";
|
|
867
|
+
tag.textContent = [
|
|
868
|
+
"@keyframes dsh-memory-pulse { 0% { opacity: 0.35; transform: scale(0.8); } 50% { opacity: 1; transform: scale(1.08); } 100% { opacity: 0.35; transform: scale(0.8); } }",
|
|
869
|
+
"@keyframes dsh-memory-pop-in { from { opacity: 0; transform: translateY(6px) scale(0.96); } to { opacity: 1; transform: none; } }",
|
|
870
|
+
".dshm-layer { position: relative; flex: 1 1 auto; display: flex; align-items: center; min-width: 0; width: calc(100% + 4px); height: 42px; margin: 4px -2px; }",
|
|
871
|
+
".dshm-layer[data-rail=\"true\"] { flex: none; width: 36px; height: 36px; margin: 8px 0 0; }",
|
|
872
|
+
".dshm-foot { flex: 1; display: flex; align-items: center; gap: 8px; min-width: 0; height: 42px; margin: 0; padding: 0 10px 0 8px; box-sizing: border-box; border: none; border-radius: 12px; background: transparent; color: var(--dsw-alias-label-primary, #eee); font-family: inherit; font-size: 14px; line-height: 22px; cursor: pointer; overflow: hidden; transition: background 150ms ease; }",
|
|
873
|
+
".dshm-foot:hover, .dshm-foot[data-open=\"true\"] { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,0.12)); }",
|
|
874
|
+
".dshm-layer[data-rail=\"true\"] .dshm-foot { flex: none; justify-content: center; gap: 0; width: 36px; height: 36px; padding: 0; border-radius: 50%; }",
|
|
875
|
+
".dshm-icon { position: relative; display: inline-flex; flex: none; align-items: center; justify-content: center; }",
|
|
876
|
+
".dshm-dot { position: absolute; right: -1px; bottom: -1px; width: 7px; height: 7px; border-radius: 50%; background: var(--dsw-alias-brand-primary, #4b7bec); animation: dsh-memory-pulse 1.4s ease-in-out infinite; }",
|
|
877
|
+
".dshm-foot-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
|
|
878
|
+
".dshm-foot-state { flex: none; margin-left: auto; font-size: 12px; line-height: 16px; color: var(--dsw-alias-label-tertiary, rgba(128,128,128,0.9)); }",
|
|
879
|
+
".dshm-foot-state[data-active=\"true\"] { color: var(--dsw-alias-brand-primary, #4b7bec); }",
|
|
880
|
+
".dshm-foot-state[data-paused=\"true\"] { color: var(--dsw-alias-state-error-primary, #e05561); }",
|
|
881
|
+
".dshm-panel { animation: dsh-memory-pop-in 160ms ease; }",
|
|
882
|
+
".dshm-btn { transition: border-color 150ms ease, background 150ms ease, opacity 150ms ease, transform 100ms ease; }",
|
|
883
|
+
".dshm-btn:hover:not(:disabled) { border-color: var(--dsw-alias-brand-primary, #4b7bec); opacity: 1; background: rgba(75, 123, 236, 0.12); }",
|
|
884
|
+
".dshm-btn:active:not(:disabled) { transform: translateY(1px); }",
|
|
885
|
+
".dshm-preview::-webkit-scrollbar { width: 6px; }",
|
|
886
|
+
".dshm-preview::-webkit-scrollbar-thumb { background: rgba(128,128,128,0.35); border-radius: 3px; }",
|
|
887
|
+
".dshm-switch { box-sizing: border-box; position: relative; flex: 0 0 auto; width: 36px; height: 20px; padding: 2px; border: 0; border-radius: 10px; corner-shape: round; background: var(--dsw-alias-border-l3, rgba(128,128,128,0.45)); cursor: pointer; }",
|
|
888
|
+
".dshm-switch[aria-checked=\"true\"] { background: var(--dsw-alias-brand-primary, #4b7bec); }",
|
|
889
|
+
".dshm-switch:disabled { cursor: default; opacity: 0.5; }",
|
|
890
|
+
".dshm-switch:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #4b7bec); outline-offset: 2px; }",
|
|
891
|
+
".dshm-thumb { display: block; width: 16px; height: 16px; border-radius: 50%; corner-shape: round; background: var(--dsw-alias-label-primary-foreground, #fff); transition: transform 120ms ease; }",
|
|
892
|
+
".dshm-switch[aria-checked=\"true\"] .dshm-thumb { transform: translateX(16px); }",
|
|
893
|
+
".dshm-toggle-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 3px 0; }"
|
|
894
|
+
].join("\n");
|
|
895
|
+
document.head.appendChild(tag);
|
|
896
|
+
styleInjected$1 = true;
|
|
897
|
+
}
|
|
898
|
+
/** The footer action component (slot component props: t + hooks + scopes). */
|
|
899
|
+
function MemoryFooterAction(props) {
|
|
900
|
+
try {
|
|
901
|
+
return renderAction(props);
|
|
902
|
+
} catch {
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
function renderAction(props) {
|
|
907
|
+
if (props.useMemoryStatus == null) return null;
|
|
908
|
+
ensureStyles();
|
|
909
|
+
const t = props.t;
|
|
910
|
+
const wide = props.wide !== false;
|
|
911
|
+
const statusSnapshot = props.useMemoryStatus((value) => value);
|
|
912
|
+
const status = statusSnapshot?.status === "ready" ? statusSnapshot.value ?? {} : {};
|
|
913
|
+
const phase = status.phase ?? "idle";
|
|
914
|
+
const active = phase === "phase1" || phase === "phase2";
|
|
915
|
+
const phaseLabel = active ? phase === "phase1" ? t("phase1") : t("phase2") : t("statusIdle");
|
|
916
|
+
const settingsSnapshot = useScopeSnapshot(props.settings);
|
|
917
|
+
const settings = settingsSnapshot?.value ?? {};
|
|
918
|
+
const settingsWritable = settingsSnapshot?.writable === true;
|
|
919
|
+
const scheduled = settings.generateMemories !== false;
|
|
920
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
921
|
+
const rootRef = (0, react.useRef)(null);
|
|
922
|
+
const [anchor, setAnchor] = (0, react.useState)(void 0);
|
|
923
|
+
(0, react.useLayoutEffect)(() => {
|
|
924
|
+
if (!open) return void 0;
|
|
925
|
+
const place = () => {
|
|
926
|
+
const rect = rootRef.current?.getBoundingClientRect();
|
|
927
|
+
if (rect != null) setAnchor({
|
|
928
|
+
left: rect.left,
|
|
929
|
+
bottom: window.innerHeight - rect.top + 8
|
|
930
|
+
});
|
|
931
|
+
};
|
|
932
|
+
place();
|
|
933
|
+
window.addEventListener("resize", place);
|
|
934
|
+
return () => {
|
|
935
|
+
window.removeEventListener("resize", place);
|
|
936
|
+
};
|
|
937
|
+
}, [open, wide]);
|
|
938
|
+
(0, react.useEffect)(() => {
|
|
939
|
+
if (!open) return void 0;
|
|
940
|
+
const onPointerDown = (event) => {
|
|
941
|
+
const root = rootRef.current;
|
|
942
|
+
const target = event.target;
|
|
943
|
+
if (root != null && target instanceof Node && root.contains(target)) return;
|
|
944
|
+
setOpen(false);
|
|
945
|
+
};
|
|
946
|
+
const onKeyDown = (event) => {
|
|
947
|
+
if (event.key === "Escape") setOpen(false);
|
|
948
|
+
};
|
|
949
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
950
|
+
document.addEventListener("keydown", onKeyDown);
|
|
951
|
+
return () => {
|
|
952
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
953
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
954
|
+
};
|
|
955
|
+
}, [open]);
|
|
956
|
+
const sendCommand = (action) => {
|
|
957
|
+
const commands = props.commands;
|
|
958
|
+
if (commands == null) return;
|
|
959
|
+
(async () => {
|
|
960
|
+
await commands.set("action", action);
|
|
961
|
+
await commands.set("requestedAt", Date.now());
|
|
962
|
+
})();
|
|
963
|
+
};
|
|
964
|
+
const toggleSetting = (field, value) => {
|
|
965
|
+
props.settings?.set(field, value);
|
|
966
|
+
};
|
|
967
|
+
const icon = (size) => (0, react.createElement)("span", { className: "dshm-icon" }, (0, react.createElement)(MemoryIcon, { size }), active ? (0, react.createElement)("span", { className: "dshm-dot" }) : null);
|
|
968
|
+
const trigger = (0, react.createElement)("button", {
|
|
969
|
+
type: "button",
|
|
970
|
+
className: "dshm-foot",
|
|
971
|
+
"data-open": open ? "true" : "false",
|
|
972
|
+
"aria-expanded": open,
|
|
973
|
+
"aria-label": t("nav"),
|
|
974
|
+
title: `${t("nav")} — ${scheduled ? phaseLabel : t("pausedBadge")}`,
|
|
975
|
+
onClick: () => setOpen((value) => !value)
|
|
976
|
+
}, icon(wide ? 16 : 18), wide ? (0, react.createElement)("span", { className: "dshm-foot-label" }, t("nav")) : null, wide ? (0, react.createElement)("span", {
|
|
977
|
+
className: "dshm-foot-state",
|
|
978
|
+
"data-active": active ? "true" : void 0,
|
|
979
|
+
"data-paused": scheduled ? void 0 : "true"
|
|
980
|
+
}, scheduled ? phaseLabel : t("pausedBadge")) : null);
|
|
981
|
+
const layer = (...children) => (0, react.createElement)("div", {
|
|
982
|
+
ref: rootRef,
|
|
983
|
+
className: "dshm-layer",
|
|
984
|
+
"data-rail": wide ? void 0 : "true"
|
|
985
|
+
}, ...children);
|
|
986
|
+
if (!open) return layer(trigger);
|
|
987
|
+
const panelStyle = {
|
|
988
|
+
position: "fixed",
|
|
989
|
+
zIndex: 40,
|
|
990
|
+
width: 320,
|
|
991
|
+
maxWidth: "calc(100vw - 24px)",
|
|
992
|
+
left: anchor?.left ?? 8,
|
|
993
|
+
bottom: anchor?.bottom ?? 56,
|
|
994
|
+
visibility: anchor == null ? "hidden" : void 0,
|
|
995
|
+
borderRadius: 14,
|
|
996
|
+
overflow: "hidden",
|
|
997
|
+
background: "var(--dsw-alias-bg-layer-3, rgba(20, 22, 28, 0.92))",
|
|
998
|
+
backdropFilter: "blur(14px)",
|
|
999
|
+
WebkitBackdropFilter: "blur(14px)",
|
|
1000
|
+
color: "var(--dsw-alias-label-primary, #eee)",
|
|
1001
|
+
border: `1px solid ${BORDER}`,
|
|
1002
|
+
boxShadow: "0 10px 32px rgba(0,0,0,0.42)"
|
|
1003
|
+
};
|
|
1004
|
+
const preview = status.summaryPreview ?? "";
|
|
1005
|
+
return layer(trigger, (0, react.createElement)("div", {
|
|
1006
|
+
className: "dshm-panel",
|
|
1007
|
+
style: panelStyle
|
|
1008
|
+
}, (0, react.createElement)("div", { style: {
|
|
1009
|
+
display: "flex",
|
|
1010
|
+
alignItems: "center",
|
|
1011
|
+
gap: 8,
|
|
1012
|
+
padding: "10px 12px",
|
|
1013
|
+
background: "var(--dsw-alias-fill-tertiary, rgba(128,128,128,0.08))",
|
|
1014
|
+
borderBottom: `1px solid ${BORDER}`
|
|
1015
|
+
} }, icon(16), (0, react.createElement)("span", { style: {
|
|
1016
|
+
fontSize: 12.5,
|
|
1017
|
+
fontWeight: 600,
|
|
1018
|
+
flex: 1
|
|
1019
|
+
} }, t("title")), (0, react.createElement)("button", {
|
|
1020
|
+
type: "button",
|
|
1021
|
+
className: "dshm-btn",
|
|
1022
|
+
title: t("minimize"),
|
|
1023
|
+
onClick: () => setOpen(false),
|
|
1024
|
+
style: {
|
|
1025
|
+
fontSize: 11,
|
|
1026
|
+
padding: "1px 8px",
|
|
1027
|
+
borderRadius: 6,
|
|
1028
|
+
cursor: "pointer",
|
|
1029
|
+
border: `1px solid ${BORDER}`,
|
|
1030
|
+
background: "transparent",
|
|
1031
|
+
color: "inherit",
|
|
1032
|
+
opacity: .75
|
|
1033
|
+
}
|
|
1034
|
+
}, t("minimize"))), (0, react.createElement)("div", { style: { padding: "10px 12px" } }, (0, react.createElement)("div", { style: {
|
|
1035
|
+
display: "flex",
|
|
1036
|
+
alignItems: "center",
|
|
1037
|
+
gap: 8
|
|
1038
|
+
} }, (0, react.createElement)("span", { style: {
|
|
1039
|
+
fontSize: 12,
|
|
1040
|
+
fontWeight: 600
|
|
1041
|
+
} }, phaseLabel), (0, react.createElement)("span", { style: {
|
|
1042
|
+
fontSize: 11,
|
|
1043
|
+
opacity: .45,
|
|
1044
|
+
marginLeft: "auto"
|
|
1045
|
+
} }, status.updatedAt ? new Date(status.updatedAt).toLocaleTimeString() : "")), status.detail !== "" ? (0, react.createElement)("div", { style: {
|
|
1046
|
+
fontSize: 11.5,
|
|
1047
|
+
opacity: .7,
|
|
1048
|
+
marginTop: 4
|
|
1049
|
+
} }, status.detail) : null, (0, react.createElement)("div", { style: {
|
|
1050
|
+
display: "flex",
|
|
1051
|
+
flexDirection: "column",
|
|
1052
|
+
gap: 5,
|
|
1053
|
+
marginTop: 10,
|
|
1054
|
+
paddingTop: 10,
|
|
1055
|
+
borderTop: `1px solid ${BORDER}`
|
|
1056
|
+
} }, ...[
|
|
1057
|
+
["enabled", "field_enabled"],
|
|
1058
|
+
["generateMemories", "field_generateMemories"],
|
|
1059
|
+
["useMemories", "field_useMemories"]
|
|
1060
|
+
].map(([field, labelKey]) => (0, react.createElement)("div", {
|
|
1061
|
+
key: field,
|
|
1062
|
+
className: "dshm-toggle-row"
|
|
1063
|
+
}, (0, react.createElement)("span", { style: {
|
|
1064
|
+
fontSize: 12,
|
|
1065
|
+
opacity: .85
|
|
1066
|
+
} }, t(labelKey)), (0, react.createElement)("button", {
|
|
1067
|
+
type: "button",
|
|
1068
|
+
role: "switch",
|
|
1069
|
+
"aria-checked": settings[field] === true,
|
|
1070
|
+
"aria-label": t(labelKey),
|
|
1071
|
+
className: "dshm-switch",
|
|
1072
|
+
disabled: !settingsWritable,
|
|
1073
|
+
onClick: () => toggleSetting(field, settings[field] !== true)
|
|
1074
|
+
}, (0, react.createElement)("span", { className: "dshm-thumb" }))))), preview !== "" ? (0, react.createElement)("div", {
|
|
1075
|
+
className: "dshm-preview",
|
|
1076
|
+
style: {
|
|
1077
|
+
marginTop: 10,
|
|
1078
|
+
padding: "8px 10px",
|
|
1079
|
+
borderRadius: 8,
|
|
1080
|
+
background: "var(--dsw-alias-fill-tertiary, rgba(128,128,128,0.08))",
|
|
1081
|
+
border: `1px solid ${BORDER}`,
|
|
1082
|
+
fontSize: 11,
|
|
1083
|
+
lineHeight: 1.5,
|
|
1084
|
+
opacity: .85,
|
|
1085
|
+
maxHeight: 130,
|
|
1086
|
+
overflowY: "auto",
|
|
1087
|
+
whiteSpace: "pre-wrap",
|
|
1088
|
+
wordBreak: "break-word"
|
|
1089
|
+
}
|
|
1090
|
+
}, preview) : null), (0, react.createElement)("div", { style: {
|
|
1091
|
+
display: "grid",
|
|
1092
|
+
gridTemplateColumns: "1fr 1fr",
|
|
1093
|
+
gap: 6,
|
|
1094
|
+
padding: "10px 12px 12px",
|
|
1095
|
+
borderTop: `1px solid ${BORDER}`
|
|
1096
|
+
} }, (0, react.createElement)("div", { style: { gridColumn: "1 / -1" } }, actionButton(t("menuRunNow"), () => sendCommand("run-cycle"), true, true)), props.openViewer != null ? actionButton(t("menuViewMemory"), () => props.openViewer?.(), false, true) : null, actionButton(scheduled ? t("menuPause") : t("menuResume"), () => sendCommand("toggle-schedule"), false, true), (0, react.createElement)("div", { style: { gridColumn: "1 / -1" } }, actionButton(t("menuOpenFolder"), () => sendCommand("open-folder"), false, true)))));
|
|
1097
|
+
}
|
|
1098
|
+
function actionButton(label, onClick, primary = false, stretch = false) {
|
|
1099
|
+
return (0, react.createElement)("button", {
|
|
1100
|
+
type: "button",
|
|
1101
|
+
onClick,
|
|
1102
|
+
className: "dshm-btn",
|
|
1103
|
+
style: {
|
|
1104
|
+
width: stretch ? "100%" : void 0,
|
|
1105
|
+
...primary ? {
|
|
1106
|
+
fontSize: 11.5,
|
|
1107
|
+
padding: "5px 10px",
|
|
1108
|
+
borderRadius: 7,
|
|
1109
|
+
cursor: "pointer",
|
|
1110
|
+
fontWeight: 600,
|
|
1111
|
+
border: "1px solid transparent",
|
|
1112
|
+
background: "var(--dsw-alias-button-info-fill, #4d6bfe)",
|
|
1113
|
+
color: "var(--dsw-alias-label-primary-inverted, #fff)"
|
|
1114
|
+
} : {
|
|
1115
|
+
fontSize: 11.5,
|
|
1116
|
+
padding: "5px 10px",
|
|
1117
|
+
borderRadius: 7,
|
|
1118
|
+
cursor: "pointer",
|
|
1119
|
+
border: `1px solid ${BORDER}`,
|
|
1120
|
+
background: "transparent",
|
|
1121
|
+
color: "inherit",
|
|
1122
|
+
opacity: .85
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
}, label);
|
|
1126
|
+
}
|
|
1127
|
+
/** The memory glyph, inlined from ui-primitives' IconDatabaseOutline16: the
|
|
1128
|
+
* browser bundle externalizes react only, so host icons cannot be imported. */
|
|
1129
|
+
function MemoryIcon({ size }) {
|
|
1130
|
+
return (0, react.createElement)("svg", {
|
|
1131
|
+
width: size,
|
|
1132
|
+
height: size,
|
|
1133
|
+
viewBox: "0 0 16 16",
|
|
1134
|
+
fill: "none",
|
|
1135
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1136
|
+
"aria-hidden": "true",
|
|
1137
|
+
focusable: "false"
|
|
1138
|
+
}, (0, react.createElement)("ellipse", {
|
|
1139
|
+
cx: 8,
|
|
1140
|
+
cy: 3.6,
|
|
1141
|
+
rx: 5.75,
|
|
1142
|
+
ry: 2.4,
|
|
1143
|
+
stroke: "currentColor",
|
|
1144
|
+
strokeWidth: 1.25
|
|
1145
|
+
}), (0, react.createElement)("path", {
|
|
1146
|
+
d: "M2.25 3.6V12.3A5.75 2.4 0 0 0 13.75 12.3V3.6",
|
|
1147
|
+
stroke: "currentColor",
|
|
1148
|
+
strokeWidth: 1.25
|
|
1149
|
+
}), (0, react.createElement)("path", {
|
|
1150
|
+
d: "M2.25 7.95A5.75 2.4 0 0 0 13.75 7.95",
|
|
1151
|
+
stroke: "currentColor",
|
|
1152
|
+
strokeWidth: 1.25
|
|
1153
|
+
}));
|
|
1154
|
+
}
|
|
1155
|
+
function useScopeSnapshot(scope) {
|
|
1156
|
+
const [snapshot, setSnapshot] = (0, react.useState)(() => scope?.getSnapshot());
|
|
1157
|
+
(0, react.useEffect)(() => {
|
|
1158
|
+
if (scope == null) return void 0;
|
|
1159
|
+
setSnapshot(scope.getSnapshot());
|
|
1160
|
+
return scope.subscribe(() => {
|
|
1161
|
+
setSnapshot(scope.getSnapshot());
|
|
1162
|
+
});
|
|
1163
|
+
}, [scope]);
|
|
1164
|
+
return snapshot;
|
|
1165
|
+
}
|
|
1166
|
+
const BORDER = "var(--dsw-alias-border-l2, rgba(255,255,255,0.14))";
|
|
1167
|
+
|
|
1168
|
+
//#endregion
|
|
1169
|
+
//#region src/client/citation-stripper.ts
|
|
1170
|
+
/**
|
|
1171
|
+
* Hides the <dsh-mem-citation> block from the rendered chat. The host-side
|
|
1172
|
+
* citation loop consumes it from the session log; this stripper keeps the raw
|
|
1173
|
+
* XML out of the visible transcript.
|
|
1174
|
+
*
|
|
1175
|
+
* Implementation: a MutationObserver watches for the marker; when found, the
|
|
1176
|
+
* text is truncated at the marker up to (and including) the end tag. React
|
|
1177
|
+
* may restore the text on re-render — the observer just strips it again.
|
|
1178
|
+
*/
|
|
1179
|
+
const MARKER_OPEN = "<dsh-mem-citation>";
|
|
1180
|
+
const MARKER_CLOSE = "</dsh-mem-citation>";
|
|
1181
|
+
/** Strip the citation block inside one element. Returns true when it cut. */
|
|
1182
|
+
function stripWithin(root) {
|
|
1183
|
+
const full = root.textContent ?? "";
|
|
1184
|
+
const start = full.indexOf(MARKER_OPEN);
|
|
1185
|
+
if (start < 0) return false;
|
|
1186
|
+
const endIndex = full.indexOf(MARKER_CLOSE, start);
|
|
1187
|
+
const cutEnd = endIndex < 0 ? full.length : endIndex + 19;
|
|
1188
|
+
let rangeStart = start;
|
|
1189
|
+
let rangeEnd = cutEnd;
|
|
1190
|
+
let cut = false;
|
|
1191
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
1192
|
+
let offset = 0;
|
|
1193
|
+
let node = walker.nextNode();
|
|
1194
|
+
while (node != null) {
|
|
1195
|
+
const length = node.data.length;
|
|
1196
|
+
if (offset + length > rangeStart && offset < rangeEnd) {
|
|
1197
|
+
const localStart = Math.max(0, rangeStart - offset);
|
|
1198
|
+
const localEnd = Math.min(length, rangeEnd - offset);
|
|
1199
|
+
node.data = node.data.slice(0, localStart) + node.data.slice(localEnd);
|
|
1200
|
+
rangeEnd -= localEnd - localStart;
|
|
1201
|
+
cut = true;
|
|
1202
|
+
}
|
|
1203
|
+
offset += node.data.length;
|
|
1204
|
+
if (offset >= rangeEnd && cut) break;
|
|
1205
|
+
node = walker.nextNode();
|
|
1206
|
+
}
|
|
1207
|
+
return cut;
|
|
1208
|
+
}
|
|
1209
|
+
function sweep(root) {
|
|
1210
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
1211
|
+
let node = walker.nextNode();
|
|
1212
|
+
while (node != null) {
|
|
1213
|
+
const next = walker.nextNode();
|
|
1214
|
+
if (node.data.includes(MARKER_OPEN)) {
|
|
1215
|
+
let scope = node.parentElement;
|
|
1216
|
+
for (let depth = 0; depth < 4 && scope != null; depth += 1) {
|
|
1217
|
+
if (stripWithin(scope)) break;
|
|
1218
|
+
scope = scope.parentElement;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
node = next;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
/** Install the stripper; returns a disposer. No-ops outside a document. */
|
|
1225
|
+
function installCitationStripper() {
|
|
1226
|
+
if (typeof document === "undefined") return () => {};
|
|
1227
|
+
let pending = false;
|
|
1228
|
+
const schedule = () => {
|
|
1229
|
+
if (pending) return;
|
|
1230
|
+
pending = true;
|
|
1231
|
+
requestAnimationFrame(() => {
|
|
1232
|
+
pending = false;
|
|
1233
|
+
try {
|
|
1234
|
+
sweep(document.body);
|
|
1235
|
+
} catch {}
|
|
1236
|
+
});
|
|
1237
|
+
};
|
|
1238
|
+
const observer = new MutationObserver((mutations) => {
|
|
1239
|
+
for (const mutation of mutations) {
|
|
1240
|
+
const target = mutation.target;
|
|
1241
|
+
if (target.nodeType === Node.TEXT_NODE && target.data.includes(MARKER_OPEN) || target instanceof Element && (target.textContent ?? "").includes(MARKER_OPEN)) {
|
|
1242
|
+
schedule();
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
observer.observe(document.body, {
|
|
1248
|
+
childList: true,
|
|
1249
|
+
subtree: true,
|
|
1250
|
+
characterData: true
|
|
1251
|
+
});
|
|
1252
|
+
sweep(document.body);
|
|
1253
|
+
return () => observer.disconnect();
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
//#endregion
|
|
1257
|
+
//#region src/client/memory-viewer.ts
|
|
1258
|
+
const MEMORY_VIEWER_KIND = "dsh-memory";
|
|
1259
|
+
const MEMORY_VIEWER_ID = "dsh-remember/viewer";
|
|
1260
|
+
const EMPTY_TEXTS = {
|
|
1261
|
+
summary: "",
|
|
1262
|
+
index: "",
|
|
1263
|
+
translatedSummary: "",
|
|
1264
|
+
translatedIndex: "",
|
|
1265
|
+
failed: false
|
|
1266
|
+
};
|
|
1267
|
+
let styleInjected = false;
|
|
1268
|
+
function ensureViewerStyles() {
|
|
1269
|
+
if (styleInjected || typeof document === "undefined") return;
|
|
1270
|
+
if (document.querySelector("style[data-dsh-memory-viewer]") != null) {
|
|
1271
|
+
styleInjected = true;
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
const tag = document.createElement("style");
|
|
1275
|
+
tag.dataset.dshMemoryViewer = "true";
|
|
1276
|
+
tag.textContent = [
|
|
1277
|
+
".dshmv-root { display: flex; flex-direction: column; height: 100%; min-height: 0; box-sizing: border-box; padding: 10px 14px 14px; color: var(--dsw-alias-label-primary, #1f2329); font-size: 13px; }",
|
|
1278
|
+
".dshmv-tabs { display: flex; gap: 2px; flex: 0 0 auto; margin-bottom: 10px; border-bottom: 0.5px solid var(--dsw-alias-border-l2, rgba(0,0,0,0.1)); }",
|
|
1279
|
+
".dshmv-tab { padding: 6px 12px; font-size: 12.5px; cursor: pointer; border: none; background: none; color: inherit; opacity: 0.55; border-bottom: 2px solid transparent; transition: opacity 140ms ease, border-color 140ms ease; }",
|
|
1280
|
+
".dshmv-tab:hover { opacity: 0.85; }",
|
|
1281
|
+
".dshmv-tab[data-active=\"true\"] { opacity: 1; border-bottom-color: var(--dsw-alias-brand-primary, #4d6bfe); font-weight: 600; }",
|
|
1282
|
+
".dshmv-toolbar { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; margin-bottom: 12px; flex-wrap: wrap; }",
|
|
1283
|
+
".dshmv-status { margin-left: auto; font-size: 11px; color: var(--dsw-alias-label-tertiary, rgba(0,0,0,0.45)); }",
|
|
1284
|
+
".dshmv-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; }",
|
|
1285
|
+
".dshmv-body[data-fill=\"true\"] { display: flex; flex-direction: column; overflow: hidden; }",
|
|
1286
|
+
".dshmv-body::-webkit-scrollbar { width: 8px; }",
|
|
1287
|
+
".dshmv-body::-webkit-scrollbar-thumb { background: var(--dsw-alias-scrollbar-bg-l2, rgba(0,0,0,0.2)); border-radius: 4px; }",
|
|
1288
|
+
".dshmv-switch-row { display: inline-flex; align-items: center; gap: 8px; font-size: 12px; color: inherit; padding: 3px 8px; border-radius: 8px; border: 0.5px solid transparent; background: none; cursor: pointer; transition: background 150ms ease, opacity 150ms ease; }",
|
|
1289
|
+
".dshmv-switch-row:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,0.05)); }",
|
|
1290
|
+
".dshmv-switch-row:disabled { opacity: 0.45; cursor: default; }",
|
|
1291
|
+
".dshmv-switch { box-sizing: border-box; position: relative; display: inline-block; flex: 0 0 auto; width: 36px; height: 20px; padding: 2px; border: 0; border-radius: 10px; corner-shape: round; background: var(--dsw-alias-border-l3, rgba(0,0,0,0.2)); transition: background 160ms ease; }",
|
|
1292
|
+
".dshmv-switch[aria-checked=\"true\"] { background: var(--dsw-alias-brand-primary, #4d6bfe); }",
|
|
1293
|
+
".dshmv-thumb { display: block; width: 16px; height: 16px; border-radius: 50%; corner-shape: round; background: var(--dsw-alias-label-primary-foreground, #fff); transition: transform 120ms ease; }",
|
|
1294
|
+
".dshmv-switch[aria-checked=\"true\"] .dshmv-thumb { transform: translateX(16px); }",
|
|
1295
|
+
".dshmv-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: 10px; margin-bottom: 14px; }",
|
|
1296
|
+
".dshmv-card { padding: 10px 12px; border-radius: 10px; border: 0.5px solid var(--dsw-alias-border-l2, rgba(0,0,0,0.1)); background: var(--dsw-alias-fill-tertiary, rgba(0,0,0,0.04)); }",
|
|
1297
|
+
".dshmv-card-num { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; }",
|
|
1298
|
+
".dshmv-card-label { font-size: 11px; color: var(--dsw-alias-label-tertiary, rgba(0,0,0,0.45)); margin-top: 2px; }",
|
|
1299
|
+
".dshmv-md { white-space: pre-wrap; word-break: break-word; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; line-height: 1.6; padding: 12px; border-radius: 10px; border: 0.5px solid var(--dsw-alias-border-l2, rgba(0,0,0,0.1)); background: var(--dsw-alias-fill-tertiary, rgba(0,0,0,0.04)); }",
|
|
1300
|
+
".dshmv-md-fill { flex: 1 1 auto; min-height: 0; overflow: auto; }",
|
|
1301
|
+
".dshmv-scroll-fill { flex: 1 1 auto; min-height: 0; overflow: auto; }",
|
|
1302
|
+
".dshmv-fill { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }",
|
|
1303
|
+
".dshmv-btn { font-size: 12px; padding: 4px 12px; border-radius: 7px; cursor: pointer; border: 0.5px solid var(--dsw-alias-border-l3, rgba(0,0,0,0.2)); background: transparent; color: inherit; transition: border-color 150ms ease, background 150ms ease, transform 100ms ease; }",
|
|
1304
|
+
".dshmv-btn:hover:not(:disabled) { border-color: var(--dsw-alias-brand-primary, #4d6bfe); background: var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,0.05)); }",
|
|
1305
|
+
".dshmv-btn:active:not(:disabled) { transform: translateY(1px); }",
|
|
1306
|
+
".dshmv-btn:disabled { opacity: 0.4; cursor: default; }",
|
|
1307
|
+
".dshmv-btn-primary { border-color: transparent; background: var(--dsw-alias-button-info-fill, #4d6bfe); color: var(--dsw-alias-label-primary-inverted, #fff); font-weight: 600; }",
|
|
1308
|
+
".dshmv-btn-primary:hover:not(:disabled) { border-color: transparent; background: var(--dsw-alias-button-info-hover, #3f5ae0); }",
|
|
1309
|
+
".dshmv-btn-danger { border-color: transparent; background: var(--dsw-alias-state-error-primary, #d54941); color: var(--dsw-alias-label-primary-inverted, #fff); font-weight: 600; }",
|
|
1310
|
+
".dshmv-btn-danger:hover:not(:disabled) { border-color: transparent; filter: brightness(1.08); }",
|
|
1311
|
+
".dshmv-confirm { display: inline-flex; align-items: center; gap: 6px; padding: 2px 6px 2px 10px; border-radius: 8px; background: rgba(213,73,65,0.12); }",
|
|
1312
|
+
".dshmv-confirm-text { font-size: 11.5px; color: var(--dsw-alias-state-error-primary, #d54941); }",
|
|
1313
|
+
".dshmv-badge { font-size: 10.5px; padding: 1px 8px; border-radius: 999px; }",
|
|
1314
|
+
".dshmv-badge-stale { background: rgba(224,172,68,0.16); color: var(--dsw-alias-state-warn-primary, #b7791f); }",
|
|
1315
|
+
".dshmv-badge-ok { background: rgba(84,196,138,0.16); color: var(--dsw-alias-state-success-primary, #2f9e63); }",
|
|
1316
|
+
".dshmv-search { width: 100%; box-sizing: border-box; font-size: 12px; padding: 6px 10px; border-radius: 8px; border: 0.5px solid var(--dsw-alias-border-l3, rgba(0,0,0,0.18)); background: var(--dsw-alias-bg-layer-1, transparent); color: inherit; margin-bottom: 10px; outline: none; }",
|
|
1317
|
+
".dshmv-search:focus { border-color: var(--dsw-alias-brand-primary, #4d6bfe); }",
|
|
1318
|
+
".dshmv-table { width: 100%; border-collapse: collapse; font-size: 12px; }",
|
|
1319
|
+
".dshmv-table th { text-align: left; font-weight: 600; font-size: 11px; color: var(--dsw-alias-label-tertiary, rgba(0,0,0,0.45)); padding: 6px 8px; border-bottom: 0.5px solid var(--dsw-alias-border-l2, rgba(0,0,0,0.1)); }",
|
|
1320
|
+
".dshmv-table td { padding: 6px 8px; border-bottom: 0.5px solid var(--dsw-alias-border-l1, rgba(0,0,0,0.06)); vertical-align: top; }",
|
|
1321
|
+
".dshmv-table tr:hover td { background: var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,0.04)); }",
|
|
1322
|
+
".dshmv-usage { display: flex; align-items: center; gap: 6px; }",
|
|
1323
|
+
".dshmv-usage-bar { height: 5px; border-radius: 3px; background: var(--dsw-alias-brand-primary, #4d6bfe); min-width: 2px; transition: width 200ms ease; }",
|
|
1324
|
+
".dshmv-empty { color: var(--dsw-alias-label-tertiary, rgba(0,0,0,0.45)); padding: 24px 0; text-align: center; font-size: 12px; }",
|
|
1325
|
+
".dshmv-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11px; color: var(--dsw-alias-label-tertiary, rgba(0,0,0,0.45)); }"
|
|
1326
|
+
].join("\n");
|
|
1327
|
+
document.head.appendChild(tag);
|
|
1328
|
+
styleInjected = true;
|
|
1329
|
+
}
|
|
1330
|
+
/** Read one namespace mirror through its selector hook (framework-synthesized). */
|
|
1331
|
+
function useMirror(hook) {
|
|
1332
|
+
const snapshot = hook?.((value) => value);
|
|
1333
|
+
return snapshot?.status === "ready" ? snapshot.value ?? {} : {};
|
|
1334
|
+
}
|
|
1335
|
+
/** Decode the gzip+base64 texts; DecompressionStream is the only browser dependency. */
|
|
1336
|
+
function useBlobs(encoded) {
|
|
1337
|
+
const [texts, setTexts] = (0, react.useState)(EMPTY_TEXTS);
|
|
1338
|
+
(0, react.useEffect)(() => {
|
|
1339
|
+
if (encoded == null || encoded === "") {
|
|
1340
|
+
setTexts(EMPTY_TEXTS);
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
let cancelled = false;
|
|
1344
|
+
decodeBlobs(encoded).then((next) => {
|
|
1345
|
+
if (!cancelled) setTexts(next);
|
|
1346
|
+
}).catch(() => {
|
|
1347
|
+
if (!cancelled) setTexts({
|
|
1348
|
+
...EMPTY_TEXTS,
|
|
1349
|
+
failed: true
|
|
1350
|
+
});
|
|
1351
|
+
});
|
|
1352
|
+
return () => {
|
|
1353
|
+
cancelled = true;
|
|
1354
|
+
};
|
|
1355
|
+
}, [encoded]);
|
|
1356
|
+
return texts;
|
|
1357
|
+
}
|
|
1358
|
+
async function decodeBlobs(encoded) {
|
|
1359
|
+
if (typeof DecompressionStream === "undefined") return {
|
|
1360
|
+
...EMPTY_TEXTS,
|
|
1361
|
+
failed: true
|
|
1362
|
+
};
|
|
1363
|
+
const binary = atob(encoded);
|
|
1364
|
+
const bytes = new Uint8Array(binary.length);
|
|
1365
|
+
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
1366
|
+
const stream = new DecompressionStream("gzip");
|
|
1367
|
+
const writer = stream.writable.getWriter();
|
|
1368
|
+
writer.write(bytes);
|
|
1369
|
+
writer.close();
|
|
1370
|
+
const parsed = JSON.parse(await new Response(stream.readable).text());
|
|
1371
|
+
return {
|
|
1372
|
+
summary: parsed.summary ?? "",
|
|
1373
|
+
index: parsed.index ?? "",
|
|
1374
|
+
translatedSummary: parsed.translatedSummary ?? "",
|
|
1375
|
+
translatedIndex: parsed.translatedIndex ?? "",
|
|
1376
|
+
failed: false
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
/** Viewer body (slot component). */
|
|
1380
|
+
function MemoryViewerBody(props) {
|
|
1381
|
+
try {
|
|
1382
|
+
return renderViewer(props);
|
|
1383
|
+
} catch {
|
|
1384
|
+
return null;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
function renderViewer(props) {
|
|
1388
|
+
ensureViewerStyles();
|
|
1389
|
+
const t = props.t;
|
|
1390
|
+
const view = useMirror(props.useMemoryView);
|
|
1391
|
+
const status = useMirror(props.useMemoryStatus);
|
|
1392
|
+
const texts = useBlobs(view.blobs);
|
|
1393
|
+
const [tab, setTab] = (0, react.useState)("overview");
|
|
1394
|
+
const [showTranslated, setShowTranslated] = (0, react.useState)(false);
|
|
1395
|
+
const [confirmingRebuild, setConfirmingRebuild] = (0, react.useState)(false);
|
|
1396
|
+
const records = parseRecords(view.records);
|
|
1397
|
+
const hasTranslation = texts.translatedSummary !== "";
|
|
1398
|
+
const translating = view.translating === true;
|
|
1399
|
+
const sendCommand = (action, arg = "", force = false) => {
|
|
1400
|
+
const commands = props.commands;
|
|
1401
|
+
if (commands == null) return;
|
|
1402
|
+
(async () => {
|
|
1403
|
+
if (arg !== "") await commands.set("arg", arg);
|
|
1404
|
+
if (force) await commands.set("force", true);
|
|
1405
|
+
await commands.set("action", action);
|
|
1406
|
+
await commands.set("requestedAt", Date.now());
|
|
1407
|
+
})();
|
|
1408
|
+
};
|
|
1409
|
+
const effectiveText = showTranslated && hasTranslation ? texts.translatedSummary : texts.summary;
|
|
1410
|
+
return (0, react.createElement)("div", { className: "dshmv-root" }, (0, react.createElement)("div", { className: "dshmv-tabs" }, ...[
|
|
1411
|
+
"overview",
|
|
1412
|
+
"summary",
|
|
1413
|
+
"index",
|
|
1414
|
+
"records"
|
|
1415
|
+
].map((id) => (0, react.createElement)("button", {
|
|
1416
|
+
key: id,
|
|
1417
|
+
type: "button",
|
|
1418
|
+
className: "dshmv-tab",
|
|
1419
|
+
"data-active": tab === id ? "true" : "false",
|
|
1420
|
+
onClick: () => setTab(id)
|
|
1421
|
+
}, t(`viewer_tab_${id}`)))), (0, react.createElement)("div", { className: "dshmv-toolbar" }, (0, react.createElement)("button", {
|
|
1422
|
+
type: "button",
|
|
1423
|
+
className: "dshmv-btn",
|
|
1424
|
+
onClick: () => sendCommand("refresh-view")
|
|
1425
|
+
}, t("viewer_refresh")), (0, react.createElement)("button", {
|
|
1426
|
+
type: "button",
|
|
1427
|
+
role: "switch",
|
|
1428
|
+
"aria-checked": showTranslated && hasTranslation,
|
|
1429
|
+
disabled: !hasTranslation || translating,
|
|
1430
|
+
className: "dshmv-switch-row",
|
|
1431
|
+
onClick: () => setShowTranslated((value) => !value)
|
|
1432
|
+
}, (0, react.createElement)("span", null, t("viewer_showTranslated")), (0, react.createElement)("span", {
|
|
1433
|
+
className: "dshmv-switch",
|
|
1434
|
+
"aria-checked": showTranslated && hasTranslation ? "true" : "false"
|
|
1435
|
+
}, (0, react.createElement)("span", { className: "dshmv-thumb" }))), (0, react.createElement)("button", {
|
|
1436
|
+
type: "button",
|
|
1437
|
+
className: translating ? "dshmv-btn" : "dshmv-btn dshmv-btn-primary",
|
|
1438
|
+
disabled: translating || texts.summary === "",
|
|
1439
|
+
onClick: () => sendCommand("translate-memory", currentLang())
|
|
1440
|
+
}, translating ? `${t("viewer_translating")}${progressLabel(status.detail) === "" ? "" : ` ${progressLabel(status.detail)}`}` : hasTranslation ? t("viewer_retranslate") : t("viewer_translate")), confirmingRebuild ? (0, react.createElement)("span", { className: "dshmv-confirm" }, (0, react.createElement)("span", { className: "dshmv-confirm-text" }, t("viewer_rebuildConfirm")), (0, react.createElement)("button", {
|
|
1441
|
+
type: "button",
|
|
1442
|
+
className: "dshmv-btn dshmv-btn-danger",
|
|
1443
|
+
disabled: translating || texts.summary === "",
|
|
1444
|
+
onClick: () => {
|
|
1445
|
+
setConfirmingRebuild(false);
|
|
1446
|
+
sendCommand("translate-memory", currentLang(), true);
|
|
1447
|
+
}
|
|
1448
|
+
}, t("viewer_confirm")), (0, react.createElement)("button", {
|
|
1449
|
+
type: "button",
|
|
1450
|
+
className: "dshmv-btn",
|
|
1451
|
+
onClick: () => setConfirmingRebuild(false)
|
|
1452
|
+
}, t("viewer_cancel"))) : (0, react.createElement)("button", {
|
|
1453
|
+
type: "button",
|
|
1454
|
+
className: "dshmv-btn",
|
|
1455
|
+
disabled: translating || texts.summary === "",
|
|
1456
|
+
onClick: () => setConfirmingRebuild(true)
|
|
1457
|
+
}, t("viewer_rebuild")), showTranslated && hasTranslation && view.translatedStale === true ? (0, react.createElement)("span", { className: "dshmv-badge dshmv-badge-stale" }, t("viewer_stale")) : null, showTranslated && hasTranslation && view.translatedStale !== true ? (0, react.createElement)("span", { className: "dshmv-badge dshmv-badge-ok" }, t("viewer_fresh")) : null, (0, react.createElement)("span", { className: "dshmv-status" }, status.detail ?? "")), (0, react.createElement)("div", {
|
|
1458
|
+
className: "dshmv-body",
|
|
1459
|
+
"data-fill": "true"
|
|
1460
|
+
}, tab === "overview" ? renderOverview(t, texts, effectiveText, showTranslated, status, records) : tab === "summary" ? (0, react.createElement)(SummaryView, {
|
|
1461
|
+
t,
|
|
1462
|
+
texts,
|
|
1463
|
+
showTranslated
|
|
1464
|
+
}) : tab === "index" ? renderIndex(t, texts, showTranslated) : (0, react.createElement)(RecordsView, {
|
|
1465
|
+
t,
|
|
1466
|
+
records
|
|
1467
|
+
})));
|
|
1468
|
+
}
|
|
1469
|
+
function renderOverview(t, texts, effectiveText, showTranslated, status, records) {
|
|
1470
|
+
const totalUsage = records.reduce((sum, record) => sum + record.usageCount, 0);
|
|
1471
|
+
const consolidated = records.filter((record) => record.selectedForPhase2).length;
|
|
1472
|
+
const phase = status.phase ?? "idle";
|
|
1473
|
+
const active = phase === "phase1" || phase === "phase2";
|
|
1474
|
+
const card = (num, label) => (0, react.createElement)("div", {
|
|
1475
|
+
className: "dshmv-card",
|
|
1476
|
+
key: label
|
|
1477
|
+
}, (0, react.createElement)("div", { className: "dshmv-card-num" }, num), (0, react.createElement)("div", { className: "dshmv-card-label" }, label));
|
|
1478
|
+
return (0, react.createElement)("div", { className: "dshmv-fill" }, (0, react.createElement)("div", { className: "dshmv-cards" }, card(records.length, t("viewer_stat_records")), card(consolidated, t("viewer_stat_consolidated")), card(totalUsage, t("viewer_stat_citations")), card(formatBytes(texts.summary.length), t("viewer_stat_summarySize"))), (0, react.createElement)("div", { style: {
|
|
1479
|
+
display: "flex",
|
|
1480
|
+
alignItems: "center",
|
|
1481
|
+
gap: 8,
|
|
1482
|
+
marginBottom: 10
|
|
1483
|
+
} }, (0, react.createElement)("span", { className: `dshmv-badge ${active ? "dshmv-badge-stale" : "dshmv-badge-ok"}` }, active ? phase === "phase1" ? t("phase1") : t("phase2") : t("statusIdle")), status.updatedAt != null && status.updatedAt > 0 ? (0, react.createElement)("span", { className: "dshmv-mono" }, new Date(status.updatedAt).toLocaleString()) : null, showTranslated ? (0, react.createElement)("span", { className: "dshmv-badge dshmv-badge-ok" }, t("viewer_badgeTranslated")) : null), effectiveText !== "" || texts.summary !== "" ? (0, react.createElement)("div", { className: "dshmv-md dshmv-md-fill" }, effectiveText !== "" ? effectiveText : texts.summary) : (0, react.createElement)("div", { className: "dshmv-empty dshmv-md-fill" }, texts.failed ? t("viewer_decodeFailed") : t("viewer_empty")));
|
|
1484
|
+
}
|
|
1485
|
+
function SummaryView(props) {
|
|
1486
|
+
const { t, texts, showTranslated } = props;
|
|
1487
|
+
const text = showTranslated && texts.translatedSummary !== "" ? texts.translatedSummary : texts.summary;
|
|
1488
|
+
return text !== "" ? (0, react.createElement)("div", { className: "dshmv-md dshmv-md-fill" }, text) : (0, react.createElement)("div", { className: "dshmv-empty dshmv-fill" }, texts.failed ? t("viewer_decodeFailed") : t("viewer_empty"));
|
|
1489
|
+
}
|
|
1490
|
+
function renderIndex(t, texts, showTranslated) {
|
|
1491
|
+
const text = showTranslated && texts.translatedIndex !== "" ? texts.translatedIndex : texts.index;
|
|
1492
|
+
return text !== "" ? (0, react.createElement)("div", { className: "dshmv-md dshmv-md-fill" }, text) : (0, react.createElement)("div", { className: "dshmv-empty dshmv-fill" }, texts.failed ? t("viewer_decodeFailed") : t("viewer_empty"));
|
|
1493
|
+
}
|
|
1494
|
+
function RecordsView(props) {
|
|
1495
|
+
const { t, records } = props;
|
|
1496
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
1497
|
+
const needle = query.trim().toLowerCase();
|
|
1498
|
+
const filtered = needle === "" ? records : records.filter((record) => record.sessionId.toLowerCase().includes(needle) || record.rolloutSlug.toLowerCase().includes(needle) || record.workspacePath.toLowerCase().includes(needle));
|
|
1499
|
+
const maxUsage = Math.max(1, ...records.map((record) => record.usageCount));
|
|
1500
|
+
return (0, react.createElement)("div", { className: "dshmv-fill" }, (0, react.createElement)("input", {
|
|
1501
|
+
className: "dshmv-search",
|
|
1502
|
+
placeholder: t("viewer_search"),
|
|
1503
|
+
value: query,
|
|
1504
|
+
onChange: (event) => setQuery(event.target.value)
|
|
1505
|
+
}), (0, react.createElement)("div", { className: "dshmv-scroll-fill" }, filtered.length === 0 ? (0, react.createElement)("div", { className: "dshmv-empty" }, t("viewer_empty")) : (0, react.createElement)("table", { className: "dshmv-table" }, (0, react.createElement)("thead", null, (0, react.createElement)("tr", null, (0, react.createElement)("th", null, t("viewer_col_session")), (0, react.createElement)("th", null, t("viewer_col_usage")), (0, react.createElement)("th", null, t("viewer_col_updated")))), (0, react.createElement)("tbody", null, ...filtered.map((record) => (0, react.createElement)("tr", { key: record.sessionId }, (0, react.createElement)("td", null, (0, react.createElement)("div", { style: { fontWeight: 600 } }, record.rolloutSlug || record.sessionId.slice(0, 8)), (0, react.createElement)("div", { className: "dshmv-mono" }, record.workspacePath)), (0, react.createElement)("td", null, (0, react.createElement)("div", { className: "dshmv-usage" }, (0, react.createElement)("span", {
|
|
1506
|
+
className: "dshmv-usage-bar",
|
|
1507
|
+
style: { width: `${Math.round(record.usageCount / maxUsage * 48)}px` }
|
|
1508
|
+
}), (0, react.createElement)("span", null, String(record.usageCount))), record.lastUsage != null ? (0, react.createElement)("div", { className: "dshmv-mono" }, `${t("viewer_col_lastUsed")}: ${formatTime(record.lastUsage)}`) : null), (0, react.createElement)("td", { className: "dshmv-mono" }, formatTime(record.sourceUpdatedAt))))))));
|
|
1509
|
+
}
|
|
1510
|
+
/** Title chip for the viewer tab. */
|
|
1511
|
+
function MemoryViewerTitle(props) {
|
|
1512
|
+
return (0, react.createElement)("span", null, props.t("viewer_title"));
|
|
1513
|
+
}
|
|
1514
|
+
/** 'N/M' extracted from the host's `translate: N/M chunks` status line. */
|
|
1515
|
+
function progressLabel(detail) {
|
|
1516
|
+
return /translate:\s*(\d+\/\d+)/.exec(detail ?? "")?.[1] ?? "";
|
|
1517
|
+
}
|
|
1518
|
+
function parseRecords(raw) {
|
|
1519
|
+
if (raw == null || raw === "") return [];
|
|
1520
|
+
try {
|
|
1521
|
+
const parsed = JSON.parse(raw);
|
|
1522
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
1523
|
+
} catch {
|
|
1524
|
+
return [];
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
function formatBytes(bytes) {
|
|
1528
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
1529
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
1530
|
+
}
|
|
1531
|
+
function formatTime(unixSeconds) {
|
|
1532
|
+
if (!Number.isFinite(unixSeconds) || unixSeconds <= 0) return "—";
|
|
1533
|
+
return (/* @__PURE__ */ new Date(unixSeconds * 1e3)).toLocaleString();
|
|
1534
|
+
}
|
|
1535
|
+
/** Current UI language tag for the translate command (`zh` vs `en`). */
|
|
1536
|
+
function currentLang() {
|
|
1537
|
+
try {
|
|
1538
|
+
return (navigator.language || "zh").toLowerCase().startsWith("zh") ? "zh" : "en";
|
|
1539
|
+
} catch {
|
|
1540
|
+
return "zh";
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
//#endregion
|
|
1545
|
+
//#region src/client/index.ts
|
|
1546
|
+
const name = "dsh-remember";
|
|
1547
|
+
/** Required browser services (cordis fiber inject). */
|
|
1548
|
+
const inject = [
|
|
1549
|
+
"slots",
|
|
1550
|
+
"locale",
|
|
1551
|
+
"settingsScope",
|
|
1552
|
+
"remote",
|
|
1553
|
+
"remote.session"
|
|
1554
|
+
];
|
|
1555
|
+
const NS = "dsh-memory";
|
|
1556
|
+
const ZH = {
|
|
1557
|
+
nav: "记忆",
|
|
1558
|
+
title: "记忆系统",
|
|
1559
|
+
intro: "空闲会话自动提取、整理为可检索的文件型记忆库,并在对话中按需召回。",
|
|
1560
|
+
unavailable: "此部署未提供记忆设置(命名空间不可用)。",
|
|
1561
|
+
group_general: "通用",
|
|
1562
|
+
group_models: "模型",
|
|
1563
|
+
group_schedule: "调度与保留",
|
|
1564
|
+
group_recall: "召回注入",
|
|
1565
|
+
group_transfer: "导入 / 导出",
|
|
1566
|
+
group_advanced: "高级",
|
|
1567
|
+
field_enabled: "启用记忆系统",
|
|
1568
|
+
field_generateMemories: "自动生成记忆",
|
|
1569
|
+
field_useMemories: "注入记忆摘要",
|
|
1570
|
+
field_workspaceDir: "记忆工作区目录",
|
|
1571
|
+
field_dbPath: "SQLite 数据库路径",
|
|
1572
|
+
field_extractModel: "提取模型 (provider/model)",
|
|
1573
|
+
field_consolidationModel: "整理模型 (provider/model)",
|
|
1574
|
+
field_scanIntervalMinutes: "扫描间隔(分钟)",
|
|
1575
|
+
field_minRolloutIdleHours: "会话静置阈值(小时)",
|
|
1576
|
+
field_maxSessionAgeDays: "会话最大年龄(天)",
|
|
1577
|
+
field_maxUnusedDays: "未用记忆保留(天)",
|
|
1578
|
+
field_maxRolloutSummaries: "rollout 摘要上限",
|
|
1579
|
+
field_phase2SuccessCooldownSeconds: "整理成功冷却(秒)",
|
|
1580
|
+
field_phase2TimeoutMinutes: "整理超时(分钟)",
|
|
1581
|
+
field_phase1RetryBackoffMinutes: "提取重试退避(分钟)",
|
|
1582
|
+
field_phase1MaxRetries: "提取重试次数",
|
|
1583
|
+
field_phase1MaxPerCycle: "每轮提取上限",
|
|
1584
|
+
field_phase1MaxConcurrency: "提取并发数",
|
|
1585
|
+
field_consolidatorDenyPatterns: "整理器额外工具黑名单(正则)",
|
|
1586
|
+
field_summaryTokenLimit: "摘要注入 token 上限",
|
|
1587
|
+
field_extractInputContextRatio: "提取上下文占比",
|
|
1588
|
+
field_redactSecrets: "提取时脱敏密钥",
|
|
1589
|
+
hint_workspaceDir: "留空使用 ~/.dsh-memory/memories",
|
|
1590
|
+
hint_dbPath: "留空使用 ~/.dsh-memory/memories.sqlite",
|
|
1591
|
+
hint_extractModel: "留空使用默认模型",
|
|
1592
|
+
hint_consolidationModel: "留空使用默认模型",
|
|
1593
|
+
hint_extractInputContextRatio: "0.1 – 0.95",
|
|
1594
|
+
hint_consolidatorDenyPatterns: "整理器默认只开放 read/write/edit/glob/grep;这里的正则在白名单之上再拒绝",
|
|
1595
|
+
overridden: "已覆盖",
|
|
1596
|
+
reset: "重置",
|
|
1597
|
+
save: "保存",
|
|
1598
|
+
discard: "放弃",
|
|
1599
|
+
saving: "保存中…",
|
|
1600
|
+
failed: "保存失败",
|
|
1601
|
+
invalid: "存在无效输入",
|
|
1602
|
+
readOnly: "此文档不可写",
|
|
1603
|
+
statusIdle: "空闲",
|
|
1604
|
+
modelDefault: "跟随默认模型",
|
|
1605
|
+
catalogRefresh: "刷新模型目录",
|
|
1606
|
+
catalogLoading: "模型目录加载中…",
|
|
1607
|
+
catalogError: "模型目录加载失败,可重试或直接填写 provider/model",
|
|
1608
|
+
done: "记忆已更新",
|
|
1609
|
+
minimize: "最小化",
|
|
1610
|
+
menuExpand: "展开",
|
|
1611
|
+
pausedBadge: "已暂停",
|
|
1612
|
+
menuRunNow: "立即整理一次",
|
|
1613
|
+
menuViewMemory: "查看记忆",
|
|
1614
|
+
menuPause: "暂停自动调度",
|
|
1615
|
+
menuResume: "恢复自动调度",
|
|
1616
|
+
menuOpenFolder: "打开记忆文件夹",
|
|
1617
|
+
phase1: "记忆提取中",
|
|
1618
|
+
phase2: "记忆整理中",
|
|
1619
|
+
processing: "记忆处理中",
|
|
1620
|
+
transfer_hint: "导出生成单个 gzip 压缩的 bundle 文件;导入按内容哈希去重合并,冲突以本地为准,未压缩的 JSON 也能读。",
|
|
1621
|
+
transfer_exportPath: "导出到文件",
|
|
1622
|
+
transfer_importPath: "从文件导入",
|
|
1623
|
+
transfer_export: "导出",
|
|
1624
|
+
transfer_import: "导入",
|
|
1625
|
+
transfer_requested: "已发送,结果见上方状态栏",
|
|
1626
|
+
viewer_title: "记忆浏览",
|
|
1627
|
+
viewer_refresh: "刷新",
|
|
1628
|
+
viewer_translate: "翻译记忆",
|
|
1629
|
+
viewer_translating: "翻译中…",
|
|
1630
|
+
viewer_tab_overview: "概览",
|
|
1631
|
+
viewer_tab_summary: "摘要",
|
|
1632
|
+
viewer_tab_index: "注册表",
|
|
1633
|
+
viewer_tab_records: "记录",
|
|
1634
|
+
viewer_stat_records: "记忆记录",
|
|
1635
|
+
viewer_stat_consolidated: "已整理",
|
|
1636
|
+
viewer_stat_citations: "被引用次数",
|
|
1637
|
+
viewer_stat_summarySize: "摘要大小",
|
|
1638
|
+
viewer_original: "显示原文",
|
|
1639
|
+
viewer_translated: "显示译文",
|
|
1640
|
+
viewer_showTranslated: "显示译文",
|
|
1641
|
+
viewer_badgeTranslated: "译文",
|
|
1642
|
+
viewer_rebuild: "重建译文",
|
|
1643
|
+
viewer_rebuildConfirm: "忽略缓存全文重翻?",
|
|
1644
|
+
viewer_confirm: "确认",
|
|
1645
|
+
viewer_cancel: "取消",
|
|
1646
|
+
viewer_retranslate: "重新翻译",
|
|
1647
|
+
viewer_stale: "译文已过期",
|
|
1648
|
+
viewer_fresh: "译文为最新",
|
|
1649
|
+
viewer_empty: "暂无内容",
|
|
1650
|
+
viewer_decodeFailed: "记忆内容解码失败(浏览器不支持 DecompressionStream)",
|
|
1651
|
+
viewer_search: "搜索会话 / 路径…",
|
|
1652
|
+
viewer_col_session: "会话",
|
|
1653
|
+
viewer_col_usage: "引用",
|
|
1654
|
+
viewer_col_updated: "更新时间",
|
|
1655
|
+
viewer_col_lastUsed: "最近引用"
|
|
1656
|
+
};
|
|
1657
|
+
const EN = {
|
|
1658
|
+
nav: "Memory",
|
|
1659
|
+
title: "Memory",
|
|
1660
|
+
intro: "Idle sessions are extracted automatically, consolidated into a searchable file-based memory library, and recalled on demand.",
|
|
1661
|
+
unavailable: "Memory settings are unavailable in this deployment (namespace not served).",
|
|
1662
|
+
group_general: "General",
|
|
1663
|
+
group_models: "Models",
|
|
1664
|
+
group_schedule: "Scheduling & retention",
|
|
1665
|
+
group_recall: "Recall injection",
|
|
1666
|
+
group_transfer: "Import / Export",
|
|
1667
|
+
group_advanced: "Advanced",
|
|
1668
|
+
field_enabled: "Enable memory system",
|
|
1669
|
+
field_generateMemories: "Generate memories automatically",
|
|
1670
|
+
field_useMemories: "Inject memory summary",
|
|
1671
|
+
field_workspaceDir: "Memory workspace directory",
|
|
1672
|
+
field_dbPath: "SQLite database path",
|
|
1673
|
+
field_extractModel: "Extraction model (provider/model)",
|
|
1674
|
+
field_consolidationModel: "Consolidation model (provider/model)",
|
|
1675
|
+
field_scanIntervalMinutes: "Scan interval (minutes)",
|
|
1676
|
+
field_minRolloutIdleHours: "Session idle threshold (hours)",
|
|
1677
|
+
field_maxSessionAgeDays: "Max session age (days)",
|
|
1678
|
+
field_maxUnusedDays: "Unused memory retention (days)",
|
|
1679
|
+
field_maxRolloutSummaries: "Rollout summary cap",
|
|
1680
|
+
field_phase2SuccessCooldownSeconds: "Consolidation cooldown (seconds)",
|
|
1681
|
+
field_phase2TimeoutMinutes: "Consolidation timeout (minutes)",
|
|
1682
|
+
field_phase1RetryBackoffMinutes: "Extraction retry backoff (minutes)",
|
|
1683
|
+
field_phase1MaxRetries: "Extraction retries",
|
|
1684
|
+
field_phase1MaxPerCycle: "Extractions per cycle",
|
|
1685
|
+
field_phase1MaxConcurrency: "Extraction concurrency",
|
|
1686
|
+
field_consolidatorDenyPatterns: "Extra consolidator tool denies (regex)",
|
|
1687
|
+
field_summaryTokenLimit: "Summary injection token cap",
|
|
1688
|
+
field_extractInputContextRatio: "Extraction context ratio",
|
|
1689
|
+
field_redactSecrets: "Redact secrets during extraction",
|
|
1690
|
+
hint_workspaceDir: "Empty = ~/.dsh-memory/memories",
|
|
1691
|
+
hint_dbPath: "Empty = ~/.dsh-memory/memories.sqlite",
|
|
1692
|
+
hint_extractModel: "Empty = deployment default model",
|
|
1693
|
+
hint_consolidationModel: "Empty = deployment default model",
|
|
1694
|
+
hint_extractInputContextRatio: "0.1 – 0.95",
|
|
1695
|
+
hint_consolidatorDenyPatterns: "The consolidator only gets read/write/edit/glob/grep; these regexes deny more on top of the whitelist",
|
|
1696
|
+
overridden: "overridden",
|
|
1697
|
+
reset: "reset",
|
|
1698
|
+
save: "Save",
|
|
1699
|
+
discard: "Discard",
|
|
1700
|
+
saving: "Saving…",
|
|
1701
|
+
failed: "Save failed",
|
|
1702
|
+
invalid: "Invalid input",
|
|
1703
|
+
readOnly: "This document is read-only",
|
|
1704
|
+
statusIdle: "Idle",
|
|
1705
|
+
modelDefault: "Follow default model",
|
|
1706
|
+
catalogRefresh: "Refresh models",
|
|
1707
|
+
catalogLoading: "Loading model catalog…",
|
|
1708
|
+
catalogError: "Model catalog failed to load — retry or type provider/model directly",
|
|
1709
|
+
done: "Memory updated",
|
|
1710
|
+
minimize: "Minimize",
|
|
1711
|
+
menuExpand: "Expand",
|
|
1712
|
+
pausedBadge: "Paused",
|
|
1713
|
+
menuRunNow: "Run now",
|
|
1714
|
+
menuViewMemory: "View memories",
|
|
1715
|
+
menuPause: "Pause scheduling",
|
|
1716
|
+
menuResume: "Resume scheduling",
|
|
1717
|
+
menuOpenFolder: "Open memory folder",
|
|
1718
|
+
phase1: "Extracting memories",
|
|
1719
|
+
phase2: "Consolidating memories",
|
|
1720
|
+
processing: "Memory processing",
|
|
1721
|
+
transfer_hint: "Export writes one gzip-compressed bundle file; import merges by content hash (conflicts keep the local copy) and also accepts uncompressed JSON.",
|
|
1722
|
+
transfer_exportPath: "Export to file",
|
|
1723
|
+
transfer_importPath: "Import from file",
|
|
1724
|
+
transfer_export: "Export",
|
|
1725
|
+
transfer_import: "Import",
|
|
1726
|
+
transfer_requested: "Sent — result appears in the status bar above",
|
|
1727
|
+
viewer_title: "Memory",
|
|
1728
|
+
viewer_refresh: "Refresh",
|
|
1729
|
+
viewer_translate: "Translate memory",
|
|
1730
|
+
viewer_translating: "Translating…",
|
|
1731
|
+
viewer_tab_overview: "Overview",
|
|
1732
|
+
viewer_tab_summary: "Summary",
|
|
1733
|
+
viewer_tab_index: "Registry",
|
|
1734
|
+
viewer_tab_records: "Records",
|
|
1735
|
+
viewer_stat_records: "Records",
|
|
1736
|
+
viewer_stat_consolidated: "Consolidated",
|
|
1737
|
+
viewer_stat_citations: "Citations",
|
|
1738
|
+
viewer_stat_summarySize: "Summary size",
|
|
1739
|
+
viewer_original: "Show original",
|
|
1740
|
+
viewer_translated: "Show translation",
|
|
1741
|
+
viewer_showTranslated: "Translation",
|
|
1742
|
+
viewer_badgeTranslated: "Translated",
|
|
1743
|
+
viewer_rebuild: "Rebuild",
|
|
1744
|
+
viewer_rebuildConfirm: "Ignore cache and retranslate everything?",
|
|
1745
|
+
viewer_confirm: "Confirm",
|
|
1746
|
+
viewer_cancel: "Cancel",
|
|
1747
|
+
viewer_retranslate: "Re-translate",
|
|
1748
|
+
viewer_stale: "Translation stale",
|
|
1749
|
+
viewer_fresh: "Up to date",
|
|
1750
|
+
viewer_empty: "Nothing here yet",
|
|
1751
|
+
viewer_decodeFailed: "Could not decode memory content (browser lacks DecompressionStream)",
|
|
1752
|
+
viewer_search: "Search sessions or paths…",
|
|
1753
|
+
viewer_col_session: "Session",
|
|
1754
|
+
viewer_col_usage: "Citations",
|
|
1755
|
+
viewer_col_updated: "Updated",
|
|
1756
|
+
viewer_col_lastUsed: "Last used"
|
|
1757
|
+
};
|
|
1758
|
+
/** Activate the browser half: register the Memory settings page and the status pill. */
|
|
1759
|
+
function apply(ctx) {
|
|
1760
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
1761
|
+
zh: ZH,
|
|
1762
|
+
en: EN
|
|
1763
|
+
}), "dsh-memory: dictionaries");
|
|
1764
|
+
ctx.effect(() => installCitationStripper(), "dsh-memory: citation stripper");
|
|
1765
|
+
const cardScope = ctx.settingsScope.bind({ namespace: "memory" });
|
|
1766
|
+
const statusScope = ctx.settingsScope.bind({ namespace: "memory-status" });
|
|
1767
|
+
const commandsScope = ctx.settingsScope.bind({ namespace: "memory-commands" });
|
|
1768
|
+
const viewerScope = ctx.settingsScope.bind({ namespace: "memory-view" });
|
|
1769
|
+
const card = new MemoryCardController(cardScope, catalogLoader(ctx));
|
|
1770
|
+
const statusSource = {
|
|
1771
|
+
getSnapshot: () => statusScope.getSnapshot(),
|
|
1772
|
+
subscribe: (listener) => statusScope.subscribe(listener)
|
|
1773
|
+
};
|
|
1774
|
+
const viewSource = {
|
|
1775
|
+
getSnapshot: () => viewerScope.getSnapshot(),
|
|
1776
|
+
subscribe: (listener) => viewerScope.subscribe(listener)
|
|
1777
|
+
};
|
|
1778
|
+
const commandsView = {
|
|
1779
|
+
set: (field, value) => commandsScope.set(field, value),
|
|
1780
|
+
unset: (field) => commandsScope.unset(field)
|
|
1781
|
+
};
|
|
1782
|
+
const memoryView = {
|
|
1783
|
+
set: (field, value) => cardScope.set(field, value),
|
|
1784
|
+
unset: (field) => cardScope.unset(field),
|
|
1785
|
+
getSnapshot: () => cardScope.getSnapshot(),
|
|
1786
|
+
subscribe: (listener) => cardScope.subscribe(listener)
|
|
1787
|
+
};
|
|
1788
|
+
let sidebarRight;
|
|
1789
|
+
ctx.inject(["sidebarRightTabs", "sidebarRight"], (sctx) => {
|
|
1790
|
+
sidebarRight = sctx.sidebarRight;
|
|
1791
|
+
const tabTitle = () => ctx.locale.bind(NS)("viewer_title");
|
|
1792
|
+
sctx.effect(() => sctx.sidebarRightTabs?.register({
|
|
1793
|
+
id: MEMORY_VIEWER_ID,
|
|
1794
|
+
kind: MEMORY_VIEWER_KIND,
|
|
1795
|
+
title: tabTitle
|
|
1796
|
+
}), "dsh-memory: viewer tab type");
|
|
1797
|
+
sctx.effect(() => sctx.slots.inject("sidebar.right.pane.tab", () => sctx.slots.register({
|
|
1798
|
+
name: "sidebar.right.pane.tab",
|
|
1799
|
+
key: MEMORY_VIEWER_ID,
|
|
1800
|
+
locale: NS,
|
|
1801
|
+
inject: () => ({
|
|
1802
|
+
hooks: {
|
|
1803
|
+
memoryView: viewSource,
|
|
1804
|
+
memoryStatus: statusSource
|
|
1805
|
+
},
|
|
1806
|
+
commands: commandsView
|
|
1807
|
+
})
|
|
1808
|
+
}, MemoryViewerBody)), "dsh-memory: viewer tab body");
|
|
1809
|
+
sctx.effect(() => sctx.slots.inject("sidebar.right.pane.tab.title", () => sctx.slots.register({
|
|
1810
|
+
name: "sidebar.right.pane.tab.title",
|
|
1811
|
+
key: MEMORY_VIEWER_ID,
|
|
1812
|
+
locale: NS
|
|
1813
|
+
}, MemoryViewerTitle)), "dsh-memory: viewer tab title");
|
|
1814
|
+
});
|
|
1815
|
+
const openViewer = () => {
|
|
1816
|
+
try {
|
|
1817
|
+
sidebarRight?.openTab(MEMORY_VIEWER_KIND, {});
|
|
1818
|
+
} catch {}
|
|
1819
|
+
};
|
|
1820
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
1821
|
+
name: "settings.section",
|
|
1822
|
+
id: "memory",
|
|
1823
|
+
order: 16,
|
|
1824
|
+
label: () => t_nav(ctx),
|
|
1825
|
+
locale: NS,
|
|
1826
|
+
inject: () => ({
|
|
1827
|
+
...card.inject(),
|
|
1828
|
+
commands: commandsView,
|
|
1829
|
+
hooks: {
|
|
1830
|
+
memoryPage: card.pageStore(),
|
|
1831
|
+
memoryStatus: statusSource
|
|
1832
|
+
}
|
|
1833
|
+
})
|
|
1834
|
+
}, MemorySettingsPage));
|
|
1835
|
+
ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
|
|
1836
|
+
name: "sidebar.footer.action",
|
|
1837
|
+
id: "dsh-memory-status",
|
|
1838
|
+
order: 10,
|
|
1839
|
+
locale: NS,
|
|
1840
|
+
inject: () => ({
|
|
1841
|
+
hooks: { memoryStatus: statusSource },
|
|
1842
|
+
commands: commandsView,
|
|
1843
|
+
settings: memoryView,
|
|
1844
|
+
openViewer
|
|
1845
|
+
})
|
|
1846
|
+
}, MemoryFooterAction));
|
|
1847
|
+
}
|
|
1848
|
+
function t_nav(ctx) {
|
|
1849
|
+
return ctx.locale.bind(NS)("nav");
|
|
1850
|
+
}
|
|
1851
|
+
/** Query the Host model catalog and flatten it into selectable routes. */
|
|
1852
|
+
function catalogLoader(ctx) {
|
|
1853
|
+
const call = ctx.remote?.session?.modelCatalog;
|
|
1854
|
+
if (call == null) return null;
|
|
1855
|
+
return async () => {
|
|
1856
|
+
const result = await call.call(ctx.remote.session);
|
|
1857
|
+
if (!result?.ok || result.value == null) throw new Error("model catalog unavailable");
|
|
1858
|
+
return result.value.groups.flatMap((group) => group.models.map((model) => ({
|
|
1859
|
+
provider: group.id,
|
|
1860
|
+
providerName: group.name,
|
|
1861
|
+
model: model.id,
|
|
1862
|
+
modelName: model.name
|
|
1863
|
+
})));
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
//#endregion
|
|
1868
|
+
exports.apply = apply;
|
|
1869
|
+
exports.inject = inject;
|
|
1870
|
+
exports.name = name;
|
|
1871
|
+
return module.exports; } });
|
|
1872
|
+
//# sourceMappingURL=client.js.map
|