@klarkxy/dsh-memory 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js ADDED
@@ -0,0 +1,1206 @@
1
+ function __createDshNodeShims() {
2
+ class EventEmitter {
3
+ constructor() {
4
+ this._e = Object.create(null)
5
+ }
6
+ on(type, fn) {
7
+ (this._e[type] ||= []).push(fn)
8
+ return this
9
+ }
10
+ addListener(type, fn) {
11
+ return this.on(type, fn)
12
+ }
13
+ once(type, fn) {
14
+ const wrap = (...args) => {
15
+ this.off(type, wrap)
16
+ fn(...args)
17
+ }
18
+ return this.on(type, wrap)
19
+ }
20
+ off(type, fn) {
21
+ const list = this._e[type]
22
+ if (list) this._e[type] = list.filter((item) => item !== fn)
23
+ return this
24
+ }
25
+ removeListener(type, fn) {
26
+ return this.off(type, fn)
27
+ }
28
+ emit(type, ...args) {
29
+ const list = this._e[type]
30
+ if (!list) return false
31
+ for (const fn of list.slice()) fn(...args)
32
+ return true
33
+ }
34
+ removeAllListeners(type) {
35
+ if (type) delete this._e[type]
36
+ else this._e = Object.create(null)
37
+ return this
38
+ }
39
+ listeners(type) {
40
+ return (this._e[type] || []).slice()
41
+ }
42
+ setMaxListeners() {
43
+ return this
44
+ }
45
+ }
46
+
47
+ class Buffer extends Uint8Array {
48
+ static from(value, encodingOrOffset, length) {
49
+ if (typeof value === 'string') return new Buffer(new TextEncoder().encode(value))
50
+ if (value instanceof ArrayBuffer) return new Buffer(new Uint8Array(value, encodingOrOffset, length))
51
+ if (ArrayBuffer.isView(value)) return new Buffer(new Uint8Array(value.buffer, value.byteOffset, value.byteLength))
52
+ if (Array.isArray(value) || value && typeof value.length === 'number') return new Buffer(Uint8Array.from(value))
53
+ return new Buffer(0)
54
+ }
55
+ static alloc(size, fill) {
56
+ const buf = new Buffer(size)
57
+ if (fill !== undefined && fill !== 0) {
58
+ if (typeof fill === 'string') {
59
+ const bytes = new TextEncoder().encode(fill)
60
+ for (let i = 0; i < size; i += 1) buf[i] = bytes[i % bytes.length]
61
+ } else buf.fill(fill)
62
+ }
63
+ return buf
64
+ }
65
+ static allocUnsafe(size) {
66
+ return new Buffer(size)
67
+ }
68
+ static allocUnsafeSlow(size) {
69
+ return new Buffer(size)
70
+ }
71
+ static concat(list, totalLength) {
72
+ const length = totalLength ?? list.reduce((sum, item) => sum + item.length, 0)
73
+ const out = new Buffer(length)
74
+ let offset = 0
75
+ for (const item of list) {
76
+ out.set(item, offset)
77
+ offset += item.length
78
+ }
79
+ return out
80
+ }
81
+ static isBuffer(value) {
82
+ return value instanceof Buffer
83
+ }
84
+ static byteLength(value) {
85
+ if (typeof value === 'string') return new TextEncoder().encode(value).length
86
+ return value?.byteLength ?? value?.length ?? 0
87
+ }
88
+ static isEncoding(encoding) {
89
+ return /^(utf8|utf-8|ascii|latin1|binary|hex|base64|base64url)$/i.test(String(encoding || ''))
90
+ }
91
+ toString(encoding) {
92
+ if (encoding === 'hex') return [...this].map((byte) => byte.toString(16).padStart(2, '0')).join('')
93
+ if (encoding === 'base64') {
94
+ let binary = ''
95
+ this.forEach((byte) => {
96
+ binary += String.fromCharCode(byte)
97
+ })
98
+ return btoa(binary)
99
+ }
100
+ return new TextDecoder().decode(this)
101
+ }
102
+ slice(start, end) {
103
+ return Buffer.from(super.subarray(start, end))
104
+ }
105
+ copy(target, targetStart = 0, start = 0, end = this.length) {
106
+ const sliced = this.subarray(start, end)
107
+ target.set(sliced, targetStart)
108
+ return sliced.length
109
+ }
110
+ write(string, offset = 0) {
111
+ const bytes = new TextEncoder().encode(string)
112
+ const written = Math.min(bytes.length, this.length - offset)
113
+ this.set(bytes.subarray(0, written), offset)
114
+ return written
115
+ }
116
+ }
117
+
118
+ function inherits(ctor, superCtor) {
119
+ ctor.super_ = superCtor
120
+ ctor.prototype = Object.create(superCtor.prototype, {
121
+ constructor: { value: ctor, writable: true, configurable: true },
122
+ })
123
+ }
124
+
125
+ if (!globalThis.Buffer) globalThis.Buffer = Buffer
126
+
127
+ return {
128
+ buffer: { Buffer, SlowBuffer: Buffer, INSPECT_MAX_BYTES: 50, kMaxLength: 0x7fffffff },
129
+ stream: {
130
+ Stream: EventEmitter,
131
+ Readable: EventEmitter,
132
+ Writable: EventEmitter,
133
+ Duplex: EventEmitter,
134
+ Transform: EventEmitter,
135
+ PassThrough: EventEmitter,
136
+ },
137
+ events: { EventEmitter },
138
+ util: {
139
+ deprecate(fn) {
140
+ return fn
141
+ },
142
+ inherits,
143
+ inspect() {
144
+ return ''
145
+ },
146
+ format(value) {
147
+ return String(value)
148
+ },
149
+ types: { isBuffer: Buffer.isBuffer },
150
+ },
151
+ }
152
+ }
153
+
154
+ window.__ModuleLoader__.load({
155
+ id: "@klarkxy/dsh-memory",
156
+ factory: (dshRequire) => {
157
+ var __nodeShims;
158
+ var process = globalThis.process || { env: {}, nextTick: function (fn) { var args = [].slice.call(arguments, 1); queueMicrotask(function () { fn.apply(null, args); }); } };
159
+ var require = function (id) {
160
+ if (id === 'buffer' || id === 'stream' || id === 'util' || id === 'events') {
161
+ if (!__nodeShims) __nodeShims = __createDshNodeShims();
162
+ return __nodeShims[id];
163
+ }
164
+ return dshRequire(id);
165
+ };
166
+ var module = { exports: {} };
167
+ var exports = module.exports;
168
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
169
+ let react = require("react");
170
+ let react_jsx_runtime = require("react/jsx-runtime");
171
+ //#region ../dsh-ai-services/src/client-utils.ts
172
+ /** The Editor retains its manuscript session separately; native DSH exposes the main-view binding. */
173
+ function selectedSessionId(client) {
174
+ return client.uiWorkspace.current ? client.uiWorkspace.current.getSnapshot()?.sessionId ?? "" : client.uiSession.adapter.current.getSnapshot().key ?? "";
175
+ }
176
+ /** Owner props win; native settings only supplies close, so resolve its selected session. */
177
+ function useNativeSeat(client, props) {
178
+ const current = (0, react.useSyncExternalStore)((0, react.useCallback)((fn) => (client.uiWorkspace.current ?? client.uiSession.adapter.current).subscribe(fn), [client]), (0, react.useCallback)(() => selectedSessionId(client), [client]), () => "");
179
+ const language = (0, react.useSyncExternalStore)((0, react.useCallback)((fn) => client.locale.subscribe(fn), [client]), (0, react.useCallback)(() => client.locale.getSnapshot().active, [client]), () => "en");
180
+ const row = props && typeof props === "object" ? props : {};
181
+ const owner = row.owner && typeof row.owner === "object" ? row.owner : {};
182
+ const sessionId = typeof row.sessionId === "string" ? row.sessionId : typeof owner.sessionId === "string" ? owner.sessionId : current;
183
+ const requestedLocale = row.locale ?? owner.locale ?? language;
184
+ return {
185
+ sessionId,
186
+ locale: String(requestedLocale).startsWith("zh") ? "zh" : "en",
187
+ hidden: row.hidden === true || owner.hidden === true
188
+ };
189
+ }
190
+ /** Refresh on native log boundaries/reconnect/focus. Read-only settling is bounded to an observed job. */
191
+ function useFeatureRefresh(client, sessionId, refresh, working = false, enabled = true) {
192
+ const refreshRef = (0, react.useRef)(refresh);
193
+ refreshRef.current = refresh;
194
+ (0, react.useEffect)(() => {
195
+ if (!enabled) return;
196
+ let timer;
197
+ const queue = () => {
198
+ if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
199
+ clearTimeout(timer);
200
+ timer = setTimeout(() => refreshRef.current(), 150);
201
+ };
202
+ const stopEvents = (sessionId ? client.sessions.binding?.(sessionId)?.eventSource : void 0)?.subscribe(queue);
203
+ const stopConnection = client.connection?.generation?.subscribe(queue);
204
+ const visibility = () => {
205
+ if (document.visibilityState === "visible") queue();
206
+ };
207
+ window.addEventListener("focus", queue);
208
+ document.addEventListener("visibilitychange", visibility);
209
+ document.addEventListener("toggle", queue, true);
210
+ return () => {
211
+ clearTimeout(timer);
212
+ stopEvents?.();
213
+ stopConnection?.();
214
+ window.removeEventListener("focus", queue);
215
+ document.removeEventListener("visibilitychange", visibility);
216
+ document.removeEventListener("toggle", queue, true);
217
+ };
218
+ }, [
219
+ client,
220
+ sessionId,
221
+ enabled
222
+ ]);
223
+ (0, react.useEffect)(() => {
224
+ if (!enabled || !working) return;
225
+ let delay = 250;
226
+ const until = Date.now() + 3e5;
227
+ let timer;
228
+ const tick = () => {
229
+ if (Date.now() >= until) return;
230
+ if (document.visibilityState !== "hidden") refreshRef.current();
231
+ delay = Math.min(delay * 2, 2e3);
232
+ timer = setTimeout(tick, delay);
233
+ };
234
+ timer = setTimeout(tick, delay);
235
+ return () => clearTimeout(timer);
236
+ }, [
237
+ client,
238
+ sessionId,
239
+ working,
240
+ enabled
241
+ ]);
242
+ }
243
+ //#endregion
244
+ //#region src/contracts.ts
245
+ const MEMORY_RPC_CHANNEL = "/dsh-memory";
246
+ //#endregion
247
+ //#region src/view-lifetime.ts
248
+ function createMemoryGeneration(start = 0) {
249
+ let current = start;
250
+ return {
251
+ current: () => current,
252
+ next: () => {
253
+ current += 1;
254
+ return current;
255
+ },
256
+ isCurrent: (token) => token === current
257
+ };
258
+ }
259
+ function memoryRequestStillCurrent(input) {
260
+ return input.gate.isCurrent(input.token) && !input.signal.aborted && input.sessionId === input.viewSessionId;
261
+ }
262
+ /** Capture session, generation token, and abort lifetime before the first await. */
263
+ function beginMemoryRequest(gate, sessionId, previous) {
264
+ previous?.abort();
265
+ const controller = new AbortController();
266
+ return {
267
+ sessionId,
268
+ token: gate.next(),
269
+ signal: controller.signal,
270
+ controller
271
+ };
272
+ }
273
+ function disposeMemoryRequest(gate, controller) {
274
+ controller.abort();
275
+ gate.next();
276
+ }
277
+ function unwrapMemoryResult(result) {
278
+ if (!result.ok) throw new Error(result.error.message);
279
+ return result.value;
280
+ }
281
+ function shouldSkipMemoryRefresh(input) {
282
+ return input.busy || input.editing;
283
+ }
284
+ async function loadMemoryStatus(input) {
285
+ const captured = input.sessionId;
286
+ const raw = await input.rpc("status", captured ? { sessionId: captured } : {}, input.signal);
287
+ if (!memoryRequestStillCurrent({
288
+ token: input.token,
289
+ gate: input.gate,
290
+ signal: input.signal,
291
+ sessionId: captured,
292
+ viewSessionId: input.viewSessionId()
293
+ })) return void 0;
294
+ return unwrapMemoryResult(raw);
295
+ }
296
+ /** Read-only status peek; uses an independent signal so it cannot abort an in-flight write. */
297
+ async function peekMemoryStatus(input) {
298
+ if (shouldSkipMemoryRefresh({
299
+ busy: input.busy(),
300
+ editing: input.editing?.() === true
301
+ })) return void 0;
302
+ const token = input.token;
303
+ if (!input.gate.isCurrent(token) || token === 0) return void 0;
304
+ const next = await loadMemoryStatus({
305
+ rpc: input.rpc,
306
+ sessionId: input.sessionId,
307
+ token,
308
+ gate: input.gate,
309
+ signal: new AbortController().signal,
310
+ viewSessionId: input.viewSessionId
311
+ });
312
+ if (!next) return void 0;
313
+ if (shouldSkipMemoryRefresh({
314
+ busy: input.busy(),
315
+ editing: input.editing?.() === true
316
+ })) return void 0;
317
+ return next;
318
+ }
319
+ //#endregion
320
+ //#region src/client.tsx
321
+ const name = "dsh-memory-client";
322
+ const inject = [
323
+ "slots",
324
+ "connection",
325
+ "sessions",
326
+ "locale",
327
+ "uiWorkspace",
328
+ "uiSession"
329
+ ];
330
+ function parseSeatProps(props) {
331
+ const row = props && typeof props === "object" ? props : {};
332
+ const nested = row.owner && typeof row.owner === "object" ? row.owner : void 0;
333
+ return {
334
+ sessionId: typeof row.sessionId === "string" && row.sessionId ? row.sessionId : typeof nested?.sessionId === "string" ? nested.sessionId : "",
335
+ locale: row.locale === "en" || nested?.locale === "en" ? "en" : "zh",
336
+ hidden: row.hidden === true || nested?.hidden === true
337
+ };
338
+ }
339
+ function memoryPanelKey(sessionId, locale) {
340
+ return `${sessionId}:${locale}`;
341
+ }
342
+ function chatSummaryTitle(locale) {
343
+ return locale === "en" ? "Memory" : "记忆";
344
+ }
345
+ function candidateAvailabilityLabel(count, locale) {
346
+ if (count > 0) return locale === "en" ? `${count} to review` : `${count} 条候选`;
347
+ return locale === "en" ? "No candidates" : "暂无候选";
348
+ }
349
+ function kindLabel(kind, locale) {
350
+ if (locale === "en") return kind === "preference" ? "Preference" : kind === "project-fact" ? "Project fact" : kind === "decision" ? "Decision" : "Lesson";
351
+ return kind === "preference" ? "偏好" : kind === "project-fact" ? "项目事实" : kind === "decision" ? "决策" : "教训";
352
+ }
353
+ function statusLabel(status, locale) {
354
+ return locale === "en" ? {
355
+ candidate: "Candidate",
356
+ active: "Active",
357
+ rejected: "Rejected",
358
+ superseded: "Superseded",
359
+ revoked: "Revoked",
360
+ deleted: "Deleted"
361
+ }[status] : {
362
+ candidate: "候选",
363
+ active: "已生效",
364
+ rejected: "已拒绝",
365
+ superseded: "已替代",
366
+ revoked: "已撤销",
367
+ deleted: "已删除"
368
+ }[status];
369
+ }
370
+ function canAccept(record) {
371
+ return record.status === "candidate";
372
+ }
373
+ function canReject(record) {
374
+ return record.status === "candidate";
375
+ }
376
+ function canRevoke(record) {
377
+ return record.status === "active";
378
+ }
379
+ function t(locale, zh, en) {
380
+ return locale === "en" ? en : zh;
381
+ }
382
+ function MemoryChatShell(props) {
383
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
384
+ className: "dsh-memory-chat",
385
+ "data-testid": "memory-chat",
386
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: chatSummaryTitle(props.locale) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
387
+ className: "dsh-memory-meta",
388
+ children: candidateAvailabilityLabel(props.candidateCount, props.locale)
389
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
390
+ className: "dsh-memory-chat-body",
391
+ children: props.children
392
+ })]
393
+ });
394
+ }
395
+ function MemorySettingsPanel({ client, sessionId, locale }) {
396
+ const gate = (0, react.useRef)(createMemoryGeneration());
397
+ const sessionRef = (0, react.useRef)(sessionId);
398
+ const workRef = (0, react.useRef)(null);
399
+ sessionRef.current = sessionId;
400
+ const [status, setStatus] = (0, react.useState)();
401
+ const [draft, setDraft] = (0, react.useState)();
402
+ const [busy, setBusy] = (0, react.useState)(false);
403
+ const [error, setError] = (0, react.useState)("");
404
+ const [note, setNote] = (0, react.useState)("");
405
+ const busyRef = (0, react.useRef)(false);
406
+ const settingsDirty = (0, react.useRef)(false);
407
+ busyRef.current = busy;
408
+ async function rpc(endpoint, payload, signal) {
409
+ const result = await client.connection.rpc.call(MEMORY_RPC_CHANNEL, endpoint, payload, signal);
410
+ unwrapMemoryResult(result);
411
+ return result;
412
+ }
413
+ (0, react.useEffect)(() => {
414
+ const request = beginMemoryRequest(gate.current, sessionId, workRef.current);
415
+ workRef.current = request.controller;
416
+ setError("");
417
+ setNote("");
418
+ loadMemoryStatus({
419
+ rpc,
420
+ sessionId: request.sessionId,
421
+ token: request.token,
422
+ gate: gate.current,
423
+ signal: request.signal,
424
+ viewSessionId: () => sessionRef.current
425
+ }).then((next) => {
426
+ if (!next) return;
427
+ setStatus(next);
428
+ setDraft(next.settings);
429
+ settingsDirty.current = false;
430
+ }).catch((cause) => {
431
+ if (!memoryRequestStillCurrent({
432
+ token: request.token,
433
+ gate: gate.current,
434
+ signal: request.signal,
435
+ sessionId: request.sessionId,
436
+ viewSessionId: sessionRef.current
437
+ })) return;
438
+ setError(cause instanceof Error ? cause.message : t(locale, "无法读取记忆设置。", "Unable to load memory settings."));
439
+ });
440
+ return () => disposeMemoryRequest(gate.current, workRef.current ?? request.controller);
441
+ }, [
442
+ client,
443
+ sessionId,
444
+ locale
445
+ ]);
446
+ useFeatureRefresh(client, sessionId, () => {
447
+ peekMemoryStatus({
448
+ rpc,
449
+ sessionId: sessionRef.current,
450
+ token: gate.current.current(),
451
+ gate: gate.current,
452
+ viewSessionId: () => sessionRef.current,
453
+ busy: () => busyRef.current,
454
+ editing: () => settingsDirty.current
455
+ }).then((next) => {
456
+ if (next) {
457
+ setStatus(next);
458
+ setDraft(next.settings);
459
+ }
460
+ }).catch(() => {});
461
+ }, false, true);
462
+ async function action(run) {
463
+ const request = beginMemoryRequest(gate.current, sessionId, workRef.current);
464
+ workRef.current = request.controller;
465
+ setBusy(true);
466
+ setNote("");
467
+ setError("");
468
+ const still = () => memoryRequestStillCurrent({
469
+ token: request.token,
470
+ gate: gate.current,
471
+ signal: request.signal,
472
+ sessionId: request.sessionId,
473
+ viewSessionId: sessionRef.current
474
+ });
475
+ try {
476
+ await run(request.sessionId);
477
+ const next = await loadMemoryStatus({
478
+ rpc,
479
+ sessionId: request.sessionId,
480
+ token: request.token,
481
+ gate: gate.current,
482
+ signal: request.signal,
483
+ viewSessionId: () => sessionRef.current
484
+ });
485
+ if (!next || !still()) return;
486
+ setStatus(next);
487
+ setDraft(next.settings);
488
+ settingsDirty.current = false;
489
+ setNote(t(locale, "已保存。", "Saved."));
490
+ } catch (cause) {
491
+ if (!still()) return;
492
+ setError(cause instanceof Error ? cause.message : t(locale, "操作失败。", "Failed."));
493
+ const next = await loadMemoryStatus({
494
+ rpc,
495
+ sessionId: request.sessionId,
496
+ token: request.token,
497
+ gate: gate.current,
498
+ signal: request.signal,
499
+ viewSessionId: () => sessionRef.current
500
+ }).catch(() => void 0);
501
+ if (next && still()) {
502
+ setStatus(next);
503
+ setDraft(next.settings);
504
+ settingsDirty.current = false;
505
+ }
506
+ } finally {
507
+ if (still()) setBusy(false);
508
+ }
509
+ }
510
+ if (!status || !draft) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
511
+ className: "dsh-memory-settings",
512
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
513
+ role: "status",
514
+ children: error || t(locale, "正在读取设置…", "Loading settings…")
515
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
516
+ type: "button",
517
+ onClick: () => void action(async () => {}),
518
+ children: t(locale, "重新连接", "Reconnect")
519
+ })]
520
+ });
521
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
522
+ className: "dsh-memory-settings",
523
+ "data-testid": "memory-settings",
524
+ children: [
525
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
526
+ role: "alert",
527
+ className: "dsh-memory-error",
528
+ children: error
529
+ }),
530
+ note && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
531
+ role: "status",
532
+ children: note
533
+ }),
534
+ status.storageFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
535
+ role: "alert",
536
+ children: t(locale, "保存失败,已保留原内容。", "Save failed; previous content was kept.")
537
+ }),
538
+ !status.aiAvailable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
539
+ className: "dsh-memory-meta",
540
+ children: t(locale, "梦境整理需要单独加载 @klarkxy/dsh-ai-services。", "Dream needs @klarkxy/dsh-ai-services loaded separately.")
541
+ }),
542
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("article", {
543
+ className: "dsh-memory-card",
544
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t(locale, "写入提示", "Prompt injection") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
545
+ className: "dsh-memory-meta",
546
+ children: t(locale, "关闭后不再注入记忆;存储保留。", "Turns off injection; stored records remain.")
547
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
548
+ type: "button",
549
+ role: "switch",
550
+ className: `dsh-memory-switch${draft.injectEnabled ? " is-on" : ""}`,
551
+ "aria-checked": draft.injectEnabled,
552
+ "aria-label": draft.injectEnabled ? t(locale, "关闭记忆注入", "Disable memory injection") : t(locale, "启用记忆注入", "Enable memory injection"),
553
+ disabled: busy,
554
+ onClick: () => void action(async () => {
555
+ await rpc("settings.update", {
556
+ expectedRevision: draft.revision,
557
+ settings: {
558
+ ...editable(draft),
559
+ injectEnabled: !draft.injectEnabled
560
+ }
561
+ });
562
+ }),
563
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
564
+ className: "dsh-memory-switch-thumb",
565
+ "aria-hidden": "true"
566
+ })
567
+ })] })
568
+ }),
569
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
570
+ className: "dsh-memory-card",
571
+ children: [
572
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t(locale, "闲时梦境", "Idle Dream") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
573
+ className: "dsh-memory-meta",
574
+ children: t(locale, "闲时自动整理记忆(每天至多一次,自动生效)。", "Auto-organizes memory while idle (at most once a day, applies automatically).")
575
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
576
+ type: "button",
577
+ role: "switch",
578
+ className: `dsh-memory-switch${draft.dreamIdleEnabled ? " is-on" : ""}`,
579
+ "aria-checked": draft.dreamIdleEnabled,
580
+ "aria-label": draft.dreamIdleEnabled ? t(locale, "关闭闲时整理", "Disable idle Dream") : t(locale, "启用闲时整理", "Enable idle Dream"),
581
+ disabled: busy,
582
+ onClick: () => void action(async () => {
583
+ await rpc("settings.update", {
584
+ expectedRevision: draft.revision,
585
+ settings: {
586
+ ...editable(draft),
587
+ dreamIdleEnabled: !draft.dreamIdleEnabled
588
+ }
589
+ });
590
+ }),
591
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
592
+ className: "dsh-memory-switch-thumb",
593
+ "aria-hidden": "true"
594
+ })
595
+ })] }),
596
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t(locale, "空闲间隔(分钟)", "Idle interval (minutes)"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
597
+ type: "number",
598
+ min: 1,
599
+ max: 180,
600
+ step: 1,
601
+ disabled: busy,
602
+ value: Math.round(draft.idleMs / 6e4),
603
+ onChange: (event) => {
604
+ settingsDirty.current = true;
605
+ setDraft({
606
+ ...draft,
607
+ idleMs: Math.round(Number(event.target.value) * 6e4)
608
+ });
609
+ }
610
+ })] }),
611
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
612
+ type: "button",
613
+ disabled: busy,
614
+ onClick: () => void action(async () => {
615
+ await rpc("settings.update", {
616
+ expectedRevision: draft.revision,
617
+ settings: editable(draft)
618
+ });
619
+ }),
620
+ children: t(locale, "保存", "Save")
621
+ })
622
+ ]
623
+ })
624
+ ]
625
+ });
626
+ }
627
+ function editable(settings) {
628
+ const { revision: _revision, ...rest } = settings;
629
+ return rest;
630
+ }
631
+ function MemoryChatPanel({ client, sessionId, locale }) {
632
+ const formId = (0, react.useId)();
633
+ const gate = (0, react.useRef)(createMemoryGeneration());
634
+ const sessionRef = (0, react.useRef)(sessionId);
635
+ const workRef = (0, react.useRef)(null);
636
+ sessionRef.current = sessionId;
637
+ const [status, setStatus] = (0, react.useState)();
638
+ const [query, setQuery] = (0, react.useState)("");
639
+ const [title, setTitle] = (0, react.useState)("");
640
+ const [content, setContent] = (0, react.useState)("");
641
+ const [kind, setKind] = (0, react.useState)("preference");
642
+ const [global, setGlobal] = (0, react.useState)(false);
643
+ const [evidence, setEvidence] = (0, react.useState)("");
644
+ const [busy, setBusy] = (0, react.useState)(false);
645
+ const [error, setError] = (0, react.useState)("");
646
+ const [note, setNote] = (0, react.useState)("");
647
+ const [editing, setEditing] = (0, react.useState)(false);
648
+ const busyRef = (0, react.useRef)(false);
649
+ const editingRef = (0, react.useRef)(false);
650
+ busyRef.current = busy;
651
+ editingRef.current = editing;
652
+ async function rpc(endpoint, payload, signal) {
653
+ const result = await client.connection.rpc.call(MEMORY_RPC_CHANNEL, endpoint, payload, signal);
654
+ unwrapMemoryResult(result);
655
+ return result;
656
+ }
657
+ (0, react.useEffect)(() => {
658
+ const request = beginMemoryRequest(gate.current, sessionId, workRef.current);
659
+ workRef.current = request.controller;
660
+ setError("");
661
+ setNote("");
662
+ loadMemoryStatus({
663
+ rpc,
664
+ sessionId: request.sessionId,
665
+ token: request.token,
666
+ gate: gate.current,
667
+ signal: request.signal,
668
+ viewSessionId: () => sessionRef.current
669
+ }).then((next) => {
670
+ if (!next) return;
671
+ setStatus(next);
672
+ }).catch((cause) => {
673
+ if (!memoryRequestStillCurrent({
674
+ token: request.token,
675
+ gate: gate.current,
676
+ signal: request.signal,
677
+ sessionId: request.sessionId,
678
+ viewSessionId: sessionRef.current
679
+ })) return;
680
+ setError(cause instanceof Error ? cause.message : t(locale, "无法读取记忆。", "Unable to load memory."));
681
+ });
682
+ return () => disposeMemoryRequest(gate.current, workRef.current ?? request.controller);
683
+ }, [
684
+ client,
685
+ sessionId,
686
+ locale
687
+ ]);
688
+ useFeatureRefresh(client, sessionId, () => {
689
+ peekMemoryStatus({
690
+ rpc,
691
+ sessionId: sessionRef.current,
692
+ token: gate.current.current(),
693
+ gate: gate.current,
694
+ viewSessionId: () => sessionRef.current,
695
+ busy: () => busyRef.current,
696
+ editing: () => editingRef.current
697
+ }).then((next) => {
698
+ if (next) setStatus(next);
699
+ }).catch(() => {});
700
+ }, Boolean(status?.runningDreams?.length), Boolean(sessionId));
701
+ async function action(run) {
702
+ const request = beginMemoryRequest(gate.current, sessionId, workRef.current);
703
+ workRef.current = request.controller;
704
+ setBusy(true);
705
+ setNote("");
706
+ setError("");
707
+ const still = () => memoryRequestStillCurrent({
708
+ token: request.token,
709
+ gate: gate.current,
710
+ signal: request.signal,
711
+ sessionId: request.sessionId,
712
+ viewSessionId: sessionRef.current
713
+ });
714
+ try {
715
+ await run(request.sessionId);
716
+ const next = await loadMemoryStatus({
717
+ rpc,
718
+ sessionId: request.sessionId,
719
+ token: request.token,
720
+ gate: gate.current,
721
+ signal: request.signal,
722
+ viewSessionId: () => sessionRef.current
723
+ });
724
+ if (!next || !still()) return;
725
+ setStatus(next);
726
+ } catch (cause) {
727
+ if (!still()) return;
728
+ setError(cause instanceof Error ? cause.message : t(locale, "操作失败。", "Failed."));
729
+ const next = await loadMemoryStatus({
730
+ rpc,
731
+ sessionId: request.sessionId,
732
+ token: request.token,
733
+ gate: gate.current,
734
+ signal: request.signal,
735
+ viewSessionId: () => sessionRef.current
736
+ }).catch(() => void 0);
737
+ if (next && still()) setStatus(next);
738
+ } finally {
739
+ if (still()) setBusy(false);
740
+ }
741
+ }
742
+ const records = (status?.records ?? []).filter((record) => {
743
+ if (!query.trim()) return true;
744
+ return `${record.title}\n${record.content}`.toLowerCase().includes(query.trim().toLowerCase());
745
+ });
746
+ const dreams = (status?.dreams ?? []).slice().sort((a, b) => b.updatedAt - a.updatedAt).slice(0, 8);
747
+ const candidateCount = (status?.records ?? []).filter((record) => record.status === "candidate").length;
748
+ const body = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
749
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("header", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
750
+ className: "dsh-memory-meta",
751
+ children: status?.projectId ? t(locale, "当前项目", "This project") : t(locale, "无项目目录;全局写入需勾选。", "No project path; global write must be explicit.")
752
+ }) }),
753
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
754
+ role: "alert",
755
+ className: "dsh-memory-error",
756
+ children: error
757
+ }),
758
+ note && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
759
+ role: "status",
760
+ children: note
761
+ }),
762
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
763
+ className: "dsh-memory-search",
764
+ children: [t(locale, "搜索", "Search"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
765
+ value: query,
766
+ onChange: (event) => setQuery(event.target.value),
767
+ disabled: busy,
768
+ "aria-label": t(locale, "搜索记忆", "Search memory")
769
+ })]
770
+ }),
771
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
772
+ className: "dsh-memory-list",
773
+ children: records.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
774
+ className: "dsh-memory-meta",
775
+ children: query.trim() ? t(locale, "没有匹配的条目。", "No matching records.") : t(locale, "暂无条目。", "No records.")
776
+ }) : records.map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoryRow, {
777
+ record,
778
+ locale,
779
+ busy,
780
+ onEditingChange: setEditing,
781
+ onAccept: () => void action((captured) => rpc("records.accept", {
782
+ sessionId: captured,
783
+ id: record.id,
784
+ expectedRevision: record.revision
785
+ }).then(() => {
786
+ if (sessionRef.current === captured) setNote(t(locale, "已采纳。", "Accepted."));
787
+ })),
788
+ onReject: () => void action((captured) => rpc("records.reject", {
789
+ sessionId: captured,
790
+ id: record.id,
791
+ expectedRevision: record.revision
792
+ }).then(() => {
793
+ if (sessionRef.current === captured) setNote(t(locale, "已拒绝。", "Rejected."));
794
+ })),
795
+ onRevoke: () => void action((captured) => rpc("records.revoke", {
796
+ sessionId: captured,
797
+ id: record.id,
798
+ expectedRevision: record.revision
799
+ }).then(() => {
800
+ if (sessionRef.current === captured) setNote(t(locale, "已撤销。", "Revoked."));
801
+ })),
802
+ onDelete: () => void action((captured) => rpc("records.remove", {
803
+ sessionId: captured,
804
+ id: record.id,
805
+ expectedRevision: record.revision
806
+ }).then(() => {
807
+ if (sessionRef.current === captured) setNote(t(locale, "已删除。", "Deleted."));
808
+ })),
809
+ onSave: (nextTitle, nextContent) => void action((captured) => rpc("records.update", {
810
+ sessionId: captured,
811
+ id: record.id,
812
+ expectedRevision: record.revision,
813
+ title: nextTitle,
814
+ content: nextContent
815
+ }).then(() => {
816
+ if (sessionRef.current === captured) setNote(t(locale, "已更新。", "Updated."));
817
+ }))
818
+ }, record.id))
819
+ }),
820
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
821
+ className: "dsh-memory-add",
822
+ onSubmit: (event) => {
823
+ event.preventDefault();
824
+ action(async (captured) => {
825
+ await rpc("records.create", {
826
+ sessionId: captured,
827
+ title,
828
+ content,
829
+ kind,
830
+ global,
831
+ evidence: evidence.trim() ? [{
832
+ sessionId: captured,
833
+ seq: 0,
834
+ kind: "manual",
835
+ excerpt: evidence.trim().slice(0, 400)
836
+ }] : []
837
+ });
838
+ if (sessionRef.current !== captured) return;
839
+ setTitle("");
840
+ setContent("");
841
+ setEvidence("");
842
+ setNote(t(locale, "已添加。", "Added."));
843
+ });
844
+ },
845
+ children: [
846
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t(locale, "手动添加", "Add") }),
847
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
848
+ htmlFor: formId + "-title",
849
+ children: [t(locale, "标题", "Title"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
850
+ id: formId + "-title",
851
+ value: title,
852
+ required: true,
853
+ maxLength: 160,
854
+ disabled: busy,
855
+ onChange: (event) => setTitle(event.target.value)
856
+ })]
857
+ }),
858
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
859
+ htmlFor: formId + "-body",
860
+ children: [t(locale, "内容", "Content"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
861
+ id: formId + "-body",
862
+ value: content,
863
+ required: true,
864
+ maxLength: 4e3,
865
+ disabled: busy,
866
+ rows: 3,
867
+ onChange: (event) => setContent(event.target.value)
868
+ })]
869
+ }),
870
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t(locale, "类型", "Kind"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
871
+ value: kind,
872
+ disabled: busy,
873
+ onChange: (event) => setKind(event.target.value),
874
+ children: [
875
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
876
+ value: "preference",
877
+ children: kindLabel("preference", locale)
878
+ }),
879
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
880
+ value: "project-fact",
881
+ children: kindLabel("project-fact", locale)
882
+ }),
883
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
884
+ value: "decision",
885
+ children: kindLabel("decision", locale)
886
+ })
887
+ ]
888
+ })] }),
889
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
890
+ htmlFor: formId + "-evidence",
891
+ children: [t(locale, "依据(可选)", "Evidence (optional)"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
892
+ id: formId + "-evidence",
893
+ value: evidence,
894
+ maxLength: 400,
895
+ disabled: busy,
896
+ onChange: (event) => setEvidence(event.target.value)
897
+ })]
898
+ }),
899
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
900
+ className: "dsh-memory-check",
901
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
902
+ type: "checkbox",
903
+ checked: global,
904
+ disabled: busy,
905
+ onChange: (event) => setGlobal(event.target.checked)
906
+ }), t(locale, "写入全局(跨项目)", "Write as global")]
907
+ }),
908
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
909
+ type: "submit",
910
+ disabled: busy,
911
+ children: t(locale, "添加", "Add")
912
+ })
913
+ ]
914
+ }),
915
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DreamPanel, {
916
+ locale,
917
+ busy,
918
+ dreams,
919
+ running: Boolean(status?.runningDreams?.length),
920
+ aiAvailable: status?.aiAvailable === true,
921
+ action,
922
+ rpc
923
+ })
924
+ ] });
925
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoryChatShell, {
926
+ locale,
927
+ candidateCount,
928
+ children: body
929
+ });
930
+ }
931
+ function MemoryRow(props) {
932
+ const { record, locale } = props;
933
+ const [editing, setEditing] = (0, react.useState)(false);
934
+ const [title, setTitle] = (0, react.useState)(record.title);
935
+ const [content, setContent] = (0, react.useState)(record.content);
936
+ const scope = record.scope.kind === "global" ? t(locale, "全局", "Global") : t(locale, "项目", "Project");
937
+ function setRowEditing(next) {
938
+ setEditing(next);
939
+ props.onEditingChange?.(next);
940
+ }
941
+ if (editing) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
942
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t(locale, "标题", "Title"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
943
+ value: title,
944
+ disabled: props.busy,
945
+ onChange: (event) => setTitle(event.target.value)
946
+ })] }),
947
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t(locale, "内容", "Content"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
948
+ value: content,
949
+ disabled: props.busy,
950
+ rows: 3,
951
+ maxLength: 4e3,
952
+ onChange: (event) => setContent(event.target.value)
953
+ })] }),
954
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
955
+ className: "dsh-memory-row-actions",
956
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
957
+ type: "button",
958
+ disabled: props.busy,
959
+ onClick: () => {
960
+ props.onSave(title, content);
961
+ setRowEditing(false);
962
+ },
963
+ children: t(locale, "保存", "Save")
964
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
965
+ type: "button",
966
+ disabled: props.busy,
967
+ onClick: () => setRowEditing(false),
968
+ children: t(locale, "取消", "Cancel")
969
+ })]
970
+ })
971
+ ] });
972
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
973
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: record.title }),
974
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
975
+ className: "dsh-memory-meta",
976
+ children: [
977
+ kindLabel(record.kind, locale),
978
+ " · ",
979
+ statusLabel(record.status, locale),
980
+ " · ",
981
+ scope
982
+ ]
983
+ }),
984
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: record.content }),
985
+ record.evidence.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
986
+ className: "dsh-memory-meta",
987
+ children: [
988
+ t(locale, "依据", "Evidence"),
989
+ ":",
990
+ record.evidence.map((item) => item.excerpt ?? `${item.kind}#${item.seq}`).join(";")
991
+ ]
992
+ }),
993
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
994
+ className: "dsh-memory-row-actions",
995
+ children: [
996
+ record.status !== "deleted" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
997
+ type: "button",
998
+ disabled: props.busy,
999
+ onClick: () => {
1000
+ setTitle(record.title);
1001
+ setContent(record.content);
1002
+ setRowEditing(true);
1003
+ },
1004
+ children: t(locale, "编辑", "Edit")
1005
+ }),
1006
+ canAccept(record) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1007
+ type: "button",
1008
+ disabled: props.busy,
1009
+ onClick: props.onAccept,
1010
+ children: t(locale, "采纳", "Accept")
1011
+ }),
1012
+ canReject(record) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1013
+ type: "button",
1014
+ disabled: props.busy,
1015
+ onClick: props.onReject,
1016
+ children: t(locale, "拒绝", "Reject")
1017
+ }),
1018
+ canRevoke(record) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1019
+ type: "button",
1020
+ disabled: props.busy,
1021
+ onClick: props.onRevoke,
1022
+ children: t(locale, "撤销", "Revoke")
1023
+ }),
1024
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfirmButton, {
1025
+ disabled: props.busy,
1026
+ label: t(locale, "删除", "Delete"),
1027
+ confirmLabel: t(locale, "确认删除?", "Confirm delete?"),
1028
+ onConfirm: props.onDelete
1029
+ })
1030
+ ]
1031
+ })
1032
+ ] });
1033
+ }
1034
+ function ConfirmButton(props) {
1035
+ const [armed, setArmed] = (0, react.useState)(false);
1036
+ const timer = (0, react.useRef)(void 0);
1037
+ (0, react.useEffect)(() => () => clearTimeout(timer.current), []);
1038
+ function disarm() {
1039
+ clearTimeout(timer.current);
1040
+ setArmed(false);
1041
+ }
1042
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1043
+ type: "button",
1044
+ className: "dsh-memory-danger",
1045
+ disabled: props.disabled,
1046
+ onClick: () => {
1047
+ if (!armed) {
1048
+ setArmed(true);
1049
+ timer.current = setTimeout(() => setArmed(false), 3e3);
1050
+ return;
1051
+ }
1052
+ disarm();
1053
+ props.onConfirm();
1054
+ },
1055
+ onBlur: disarm,
1056
+ children: armed ? props.confirmLabel : props.label
1057
+ });
1058
+ }
1059
+ function dreamStatusLabel(plan, locale) {
1060
+ if (plan.status === "applied") return t(locale, "已应用", "Applied");
1061
+ if (plan.status === "noop") return t(locale, "无变化", "No change");
1062
+ if (plan.status === "failed" || plan.status === "stale") return t(locale, "失败", "Failed");
1063
+ if (plan.status === "cancelled") return t(locale, "已取消", "Cancelled");
1064
+ return plan.proposals.length ? t(locale, "未应用", "Not applied") : t(locale, "无变化", "No change");
1065
+ }
1066
+ function DreamPanel(props) {
1067
+ const locale = props.locale;
1068
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
1069
+ className: "dsh-memory-dream",
1070
+ children: [
1071
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { children: t(locale, "梦境整理", "Dream") }),
1072
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1073
+ className: "dsh-memory-meta",
1074
+ children: t(locale, "闲置且新材料足够时每天至多自动整理一次,结果自动生效。", "Runs at most once a day when idle with enough new material; results apply automatically.")
1075
+ }),
1076
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1077
+ className: "dsh-memory-row-actions",
1078
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1079
+ type: "button",
1080
+ disabled: props.busy || props.running || !props.aiAvailable,
1081
+ onClick: () => void props.action(async (captured) => {
1082
+ await props.rpc("dream.run", { sessionId: captured });
1083
+ }),
1084
+ children: t(locale, "立即整理", "Organize now")
1085
+ }), props.running && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1086
+ className: "dsh-memory-meta",
1087
+ children: t(locale, "整理中…", "Organizing…")
1088
+ })]
1089
+ }),
1090
+ props.dreams.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { children: props.dreams.map((plan) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
1091
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: dreamStatusLabel(plan, locale) }),
1092
+ " ",
1093
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1094
+ className: "dsh-memory-meta",
1095
+ children: new Date(plan.createdAt).toLocaleString(locale === "zh" ? "zh-CN" : "en-US")
1096
+ }),
1097
+ plan.proposals.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1098
+ className: "dsh-memory-meta",
1099
+ children: plan.proposals.map((proposal) => proposal.title).join("、")
1100
+ }),
1101
+ plan.error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1102
+ className: "dsh-memory-error",
1103
+ children: plan.error
1104
+ })
1105
+ ] }, plan.id)) })
1106
+ ]
1107
+ });
1108
+ }
1109
+ function MemorySettings({ client, props }) {
1110
+ const seat = useNativeSeat(client, props);
1111
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1112
+ className: "dsh-memory-settings-root",
1113
+ "data-testid": "memory-settings-root",
1114
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemorySettingsPanel, {
1115
+ client,
1116
+ sessionId: seat.sessionId,
1117
+ locale: seat.locale
1118
+ }, `settings:${memoryPanelKey(seat.sessionId, seat.locale)}`), seat.sessionId && !seat.hidden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoryChatPanel, {
1119
+ client,
1120
+ sessionId: seat.sessionId,
1121
+ locale: seat.locale
1122
+ }, `manage:${memoryPanelKey(seat.sessionId, seat.locale)}`) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1123
+ className: "dsh-memory-meta",
1124
+ children: t(seat.locale, "选择一个会话后可以管理该会话的记忆。", "Select a session to manage its memory.")
1125
+ })]
1126
+ });
1127
+ }
1128
+ const styles = `
1129
+ .dsh-memory-settings-root,.dsh-memory-settings,.dsh-memory-chat{max-width:760px;color:inherit;font:400 var(--font-size-2,14px)/1.5 var(--default-font-family,system-ui,sans-serif)}
1130
+ .dsh-memory-settings-root,.dsh-memory-settings{display:grid;gap:16px}
1131
+ .dsh-memory-chat-body{display:grid;gap:12px;margin-top:8px}
1132
+ .dsh-memory-settings p,.dsh-memory-chat p{margin:0;line-height:1.5}
1133
+ .dsh-memory-meta,.dsh-memory-chat small{font-size:var(--font-size-1,13px);color:var(--gray-11,inherit)}
1134
+ .dsh-memory-error{color:var(--red-11,#b42318)}
1135
+ .dsh-memory-card,.dsh-memory-dream,.dsh-memory-add{display:grid;gap:10px;padding:14px 16px;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent));border-radius:12px}
1136
+ .dsh-memory-card header,.dsh-memory-chat-body header{display:flex;justify-content:space-between;gap:12px;align-items:center}
1137
+ .dsh-memory-card h3,.dsh-memory-add h4,.dsh-memory-dream h4{margin:0;font-size:var(--font-size-3,16px);font-weight:600}
1138
+ .dsh-memory-settings input,.dsh-memory-chat input,.dsh-memory-chat textarea,.dsh-memory-chat select{box-sizing:border-box;width:100%;min-width:0;padding:8px 10px;border:1px solid var(--gray-6,color-mix(in srgb,currentColor 22%,transparent));border-radius:8px;background:var(--color-surface,transparent);color:inherit;font:inherit;transition:border-color 150ms ease,box-shadow 150ms ease}
1139
+ .dsh-memory-settings input:focus,.dsh-memory-chat input:focus,.dsh-memory-chat textarea:focus,.dsh-memory-chat select:focus{border-color:var(--accent-9,#3b82f6);box-shadow:0 0 0 2px color-mix(in srgb,var(--accent-9,#3b82f6) 25%,transparent)}
1140
+ .dsh-memory-settings button:not([role="switch"]),.dsh-memory-chat button:not([role="switch"]){min-height:34px;padding:6px 12px;border:1px solid color-mix(in srgb,currentColor 25%,transparent);border-radius:8px;background:transparent;color:inherit;cursor:pointer;font:inherit;justify-self:start;transition:background-color 150ms ease,color 150ms ease,border-color 150ms ease,box-shadow 150ms ease,transform 150ms ease}
1141
+ .dsh-memory-settings button:not([role="switch"]):hover:not(:disabled),.dsh-memory-chat button:not([role="switch"]):hover:not(:disabled){background:var(--gray-3,color-mix(in srgb,currentColor 6%,transparent));border-color:color-mix(in srgb,currentColor 35%,transparent)}
1142
+ .dsh-memory-settings button:not([role="switch"]):active:not(:disabled),.dsh-memory-chat button:not([role="switch"]):active:not(:disabled){transform:scale(.97)}
1143
+ .dsh-memory-danger:hover:not(:disabled){border-color:var(--red-11,#b42318);color:var(--red-11,#b42318);background:color-mix(in srgb,var(--red-11,#b42318) 8%,transparent)}
1144
+ .dsh-memory-settings button:disabled,.dsh-memory-chat button:disabled{opacity:.45;cursor:not-allowed}
1145
+ .dsh-memory-settings .dsh-memory-switch,.dsh-memory-chat .dsh-memory-switch{all:unset;box-sizing:border-box;position:relative;display:inline-block;width:36px;height:20px;flex:none;border-radius:999px;background:var(--gray-7,color-mix(in srgb,currentColor 28%,transparent));cursor:pointer}
1146
+ .dsh-memory-switch.is-on{background:var(--accent-9,#3b82f6)}
1147
+ .dsh-memory-switch-thumb{position:absolute;top:3px;left:3px;width:14px;height:14px;border-radius:999px;background:#fff;transition:transform 150ms ease}
1148
+ .dsh-memory-switch.is-on .dsh-memory-switch-thumb{transform:translateX(16px)}
1149
+ .dsh-memory-settings :focus-visible,.dsh-memory-chat :focus-visible{outline:2px solid var(--accent-9,currentColor);outline-offset:3px}
1150
+ .dsh-memory-list{list-style:none;margin:0;padding:0;display:grid;gap:12px}
1151
+ .dsh-memory-list li{display:grid;gap:6px;padding:10px 4px;border-top:1px solid var(--gray-6,color-mix(in srgb,currentColor 15%,transparent));border-radius:10px;overflow-wrap:anywhere}
1152
+ .dsh-memory-row-actions{display:flex;flex-wrap:wrap;gap:8px}
1153
+ .dsh-memory-check{display:flex;gap:8px;align-items:center}
1154
+ .dsh-memory-check input{width:auto}
1155
+ .dsh-memory-chat>summary{display:flex;flex-wrap:wrap;gap:8px 12px;align-items:baseline;cursor:pointer;min-height:34px;list-style:revert}
1156
+ @media(prefers-reduced-motion:reduce){.dsh-memory-switch-thumb{transition:none}.dsh-memory-settings button:not([role="switch"]),.dsh-memory-chat button:not([role="switch"]),.dsh-memory-settings input,.dsh-memory-chat input,.dsh-memory-chat textarea,.dsh-memory-chat select{transition:none}}
1157
+ `;
1158
+ function apply(ctx) {
1159
+ const client = ctx;
1160
+ ctx.effect(() => {
1161
+ if (typeof document === "undefined") return () => {};
1162
+ const style = document.createElement("style");
1163
+ style.setAttribute("data-plugin", "@klarkxy/dsh-memory");
1164
+ style.textContent = styles;
1165
+ document.head.appendChild(style);
1166
+ return () => style.remove();
1167
+ }, "dsh-memory.styles");
1168
+ ctx.effect(() => client.slots.inject("settings.section", () => client.slots.register({
1169
+ name: "settings.section",
1170
+ id: "memory",
1171
+ order: 65,
1172
+ label: "记忆"
1173
+ }, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemorySettings, {
1174
+ client,
1175
+ props
1176
+ }))), "dsh-memory.settings");
1177
+ }
1178
+ //#endregion
1179
+ exports.MemoryChatShell = MemoryChatShell;
1180
+ exports.MemorySettings = MemorySettings;
1181
+ exports.apply = apply;
1182
+ exports.beginMemoryRequest = beginMemoryRequest;
1183
+ exports.canAccept = canAccept;
1184
+ exports.canReject = canReject;
1185
+ exports.canRevoke = canRevoke;
1186
+ exports.candidateAvailabilityLabel = candidateAvailabilityLabel;
1187
+ exports.chatSummaryTitle = chatSummaryTitle;
1188
+ exports.createMemoryGeneration = createMemoryGeneration;
1189
+ exports.disposeMemoryRequest = disposeMemoryRequest;
1190
+ exports.dreamStatusLabel = dreamStatusLabel;
1191
+ exports.inject = inject;
1192
+ exports.kindLabel = kindLabel;
1193
+ exports.loadMemoryStatus = loadMemoryStatus;
1194
+ exports.memoryPanelKey = memoryPanelKey;
1195
+ exports.memoryRequestStillCurrent = memoryRequestStillCurrent;
1196
+ exports.name = name;
1197
+ exports.parseSeatProps = parseSeatProps;
1198
+ exports.peekMemoryStatus = peekMemoryStatus;
1199
+ exports.shouldSkipMemoryRefresh = shouldSkipMemoryRefresh;
1200
+ exports.statusLabel = statusLabel;
1201
+ exports.unwrapMemoryResult = unwrapMemoryResult;
1202
+
1203
+ //# sourceMappingURL=client.inner.cjs.map
1204
+ return module.exports;
1205
+ }
1206
+ });