@a9i5k4/dsh-auto-memory 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js ADDED
@@ -0,0 +1,662 @@
1
+ /* dsh-auto-memory — browser half (hand-written __ModuleLoader__ bundle).
2
+ * Registers three additive surfaces:
3
+ * 1. sidebar.footer.action — 「记忆」入口按钮(开关左下角浮层面板)
4
+ * 2. shell.overlay — 记忆面板:概览 / 日志 / 笔记 / 反思 / 检索
5
+ * 液态玻璃视觉(backdrop-filter + --dsw-alias-* 主题令牌),
6
+ * 可拖动 / 右下角缩放 / 开关缩放动画 / 位置大小持久化。
7
+ * 3. settings.section — 自动记忆设置页(存储位置、注入、反思风格)
8
+ * Data flows over /api/dsh-auto-memory/* (loopback-only host routes).
9
+ */
10
+ window.__ModuleLoader__.load({
11
+ id: '@a9i5k4/dsh-auto-memory',
12
+ factory: (require) => {
13
+ var module = { exports: {} }
14
+ var exports = module.exports
15
+ var React = require('react')
16
+ var h = React.createElement
17
+ var useState = React.useState
18
+ var useEffect = React.useEffect
19
+ var useReducer = React.useReducer
20
+
21
+ // ───────────────────────── 控制器 ─────────────────────────
22
+ var listeners = new Set()
23
+ var panelOpen = false
24
+ var panelClosing = false
25
+ var closeTimer = null
26
+ // 几何状态:left/top/width/height,持久化到 localStorage(用户可拖动/缩放)
27
+ var GEOM_KEY = 'dsh-auto-memory.panel.geom'
28
+ var DEFAULT_W = 440
29
+ var DEFAULT_H = 560
30
+ var DEFAULT_GAP = 16
31
+ function defaultGeom() {
32
+ var vh = window.innerHeight || 800
33
+ return {
34
+ left: DEFAULT_GAP,
35
+ top: Math.max(DEFAULT_GAP, vh - DEFAULT_H - DEFAULT_GAP),
36
+ width: DEFAULT_W,
37
+ height: DEFAULT_H,
38
+ }
39
+ }
40
+ var geom = null
41
+ function clampGeom(g) {
42
+ var vw = window.innerWidth || 1280
43
+ var vh = window.innerHeight || 800
44
+ var w = Math.max(300, Math.min(g.width || DEFAULT_W, vw - DEFAULT_GAP * 2))
45
+ var h = Math.max(240, Math.min(g.height || DEFAULT_H, vh - DEFAULT_GAP * 2))
46
+ return {
47
+ left: Math.max(DEFAULT_GAP, Math.min(g.left !== undefined ? g.left : DEFAULT_GAP, vw - w - DEFAULT_GAP)),
48
+ top: Math.max(DEFAULT_GAP, Math.min(g.top !== undefined ? g.top : DEFAULT_GAP, vh - h - DEFAULT_GAP)),
49
+ width: w,
50
+ height: h,
51
+ }
52
+ }
53
+ function loadGeom() {
54
+ try {
55
+ var raw = localStorage.getItem(GEOM_KEY)
56
+ if (raw) return clampGeom(JSON.parse(raw))
57
+ } catch (e) {}
58
+ return clampGeom(defaultGeom())
59
+ }
60
+ function persistGeom() { if (geom) { try { localStorage.setItem(GEOM_KEY, JSON.stringify(geom)) } catch (e) {} } }
61
+ function emit() { listeners.forEach(function (fn) { try { fn() } catch (e) {} }) }
62
+ var controller = {
63
+ isOpen: function () { return panelOpen },
64
+ isClosing: function () { return panelClosing },
65
+ geom: function () { if (!geom) geom = loadGeom(); return geom },
66
+ setGeom: function (partial) {
67
+ var cur = controller.geom()
68
+ geom = clampGeom({
69
+ left: partial.left !== undefined ? partial.left : cur.left,
70
+ top: partial.top !== undefined ? partial.top : cur.top,
71
+ width: partial.width !== undefined ? partial.width : cur.width,
72
+ height: partial.height !== undefined ? partial.height : cur.height,
73
+ })
74
+ emit()
75
+ },
76
+ flushGeom: function () { persistGeom() },
77
+ resetGeom: function () { geom = clampGeom(defaultGeom()); persistGeom(); emit() },
78
+ toggle: function () { if (panelOpen) controller.close(); else controller.open() },
79
+ open: function () {
80
+ if (closeTimer) { clearTimeout(closeTimer); closeTimer = null }
81
+ panelOpen = true; panelClosing = false; emit()
82
+ },
83
+ close: function () {
84
+ if (!panelOpen || panelClosing) return
85
+ panelClosing = true; emit()
86
+ closeTimer = setTimeout(function () {
87
+ panelOpen = false; panelClosing = false; closeTimer = null; emit()
88
+ }, 170)
89
+ },
90
+ subscribe: function (fn) { listeners.add(fn); return function () { listeners.delete(fn) } },
91
+ }
92
+
93
+ // ───────────────────────── API ─────────────────────────
94
+ var API = {
95
+ state: '/api/dsh-auto-memory/state',
96
+ list: '/api/dsh-auto-memory/list',
97
+ file: '/api/dsh-auto-memory/file',
98
+ recall: '/api/dsh-auto-memory/recall',
99
+ config: '/api/dsh-auto-memory/config',
100
+ reflect: '/api/dsh-auto-memory/reflect',
101
+ reflectAuto: '/api/dsh-auto-memory/reflect-auto',
102
+ note: '/api/dsh-auto-memory/note',
103
+ external: '/api/dsh-auto-memory/external',
104
+ externalImport: '/api/dsh-auto-memory/external-import',
105
+ }
106
+ function query(params) {
107
+ var search = new URLSearchParams()
108
+ for (var key in params) if (params[key] !== undefined && params[key] !== '') search.set(key, String(params[key]))
109
+ var text = search.toString()
110
+ return text ? '?' + text : ''
111
+ }
112
+ async function apiGet(path, params) {
113
+ var res = await fetch(path + query(params))
114
+ var body = await res.json().catch(function () { return {} })
115
+ if (!res.ok) throw new Error(body.error || ('HTTP ' + res.status))
116
+ return body
117
+ }
118
+ async function apiPost(path, payload) {
119
+ var res = await fetch(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload) })
120
+ var body = await res.json().catch(function () { return {} })
121
+ if (!res.ok) throw new Error(body.error || ('HTTP ' + res.status))
122
+ return body
123
+ }
124
+
125
+ // ───────────────────────── 样式 ─────────────────────────
126
+ // 视觉:液态玻璃(毛玻璃)—— backdrop-filter + DSH 主题令牌(--dsw-alias-*),
127
+ // 跟随亮/暗主题与页面背景自适应;位置/尺寸由 JS 几何状态驱动(可拖动、可缩放)。
128
+ var CSS = [
129
+ '[data-dam-panel] { position: fixed; left: 16px; width: 440px; height: 560px;',
130
+ ' max-width: calc(100vw - 32px); max-height: calc(100vh - 32px);',
131
+ ' display: flex; flex-direction: column; overflow: hidden; z-index: 3000; pointer-events: auto;',
132
+ ' border-radius: 16px; font: 13px/1.55 system-ui, "Segoe UI", sans-serif;',
133
+ ' background: color-mix(in srgb, var(--dsw-alias-bg-overlay, rgba(255,255,255,.86)) 58%, transparent);',
134
+ ' -webkit-backdrop-filter: blur(28px) saturate(1.55); backdrop-filter: blur(28px) saturate(1.55);',
135
+ ' border: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.35)) 65%, transparent);',
136
+ ' box-shadow: 0 24px 64px rgba(0,0,0,.22), 0 4px 16px rgba(0,0,0,.10), inset 0 1px 0 rgba(255,255,255,.22);',
137
+ ' color: var(--dsw-alias-label-primary, #1f2328);',
138
+ ' animation: dam-in .2s cubic-bezier(.2,.9,.3,1.15) both; transform-origin: left bottom; }',
139
+ '@keyframes dam-in { from { opacity: 0; transform: scale(.92) translateY(10px); } to { opacity: 1; transform: none; } }',
140
+ '@keyframes dam-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: scale(.95) translateY(6px); } }',
141
+ '[data-dam-panel][data-closing="true"] { animation: dam-out .16s ease-in both; }',
142
+ '[data-dam-panel]::before { content: ""; position: absolute; inset: 0 0 auto 0; height: 64%; pointer-events: none;',
143
+ ' background: linear-gradient(180deg, rgba(255,255,255,.13), rgba(255,255,255,0) 70%); border-radius: 16px 16px 0 0; }',
144
+ '[data-dam-panel][data-dragging="true"] { user-select: none; }',
145
+ '[data-dam-panel] header { display: flex; align-items: center; gap: 8px; padding: 10px 14px; cursor: grab;',
146
+ ' border-bottom: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.25)) 55%, transparent); }',
147
+ '[data-dam-panel][data-dragging="true"] header { cursor: grabbing; }',
148
+ '[data-dam-panel] header strong { font-size: 14px; }',
149
+ '[data-dam-panel] header .dam-spacer { flex: 1; }',
150
+ '[data-dam-resize] { position: absolute; right: 0; bottom: 0; width: 22px; height: 22px; cursor: nwse-resize;',
151
+ ' opacity: .55; z-index: 2; border-radius: 0 0 16px 0;',
152
+ ' background: linear-gradient(135deg, transparent 50%, color-mix(in srgb, var(--dsw-alias-label-secondary, #666) 45%, transparent) 50%); }',
153
+ '[data-dam-resize]:hover { opacity: 1; }',
154
+ '[data-dam-btn] { border: none; background: transparent; cursor: pointer; color: inherit; opacity: .75; font-size: 13px; padding: 4px 8px; border-radius: 6px; }',
155
+ '[data-dam-btn]:hover { opacity: 1; background: color-mix(in srgb, var(--dsw-alias-label-secondary, #666) 16%, transparent); }',
156
+ '[data-dam-tabs] { display: flex; gap: 2px; padding: 6px 10px; border-bottom: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.2)) 55%, transparent); }',
157
+ '[data-dam-tab] { border: none; background: transparent; cursor: pointer; color: inherit; opacity: .6; padding: 5px 10px; border-radius: 7px; font-size: 12.5px; }',
158
+ '[data-dam-tab][data-active="true"] { opacity: 1; background: color-mix(in srgb, var(--dsw-alias-label-secondary, #666) 18%, transparent); font-weight: 600; }',
159
+ '[data-dam-body] { flex: 1; overflow: auto; padding: 12px 14px; }',
160
+ '[data-dam-kv] { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 12.5px; }',
161
+ '[data-dam-kv] b { opacity: .55; font-weight: 500; }',
162
+ '[data-dam-card] { border: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.22)) 60%, transparent); border-radius: 9px; padding: 8px 10px; margin-bottom: 8px; font-size: 12.5px;',
163
+ ' background: color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.06)) 40%, transparent); }',
164
+ '[data-dam-card] .dam-date { font-weight: 600; margin-bottom: 4px; }',
165
+ '[data-dam-card] .dam-content { white-space: pre-wrap; word-break: break-word; max-height: 220px; overflow: auto; }',
166
+ '[data-dam-banner] { border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary, #e6a23c) 55%, transparent);',
167
+ ' background: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #e6a23c) 13%, transparent); border-radius: 9px; padding: 8px 10px; margin-bottom: 10px; font-size: 12.5px; }',
168
+ '[data-dam-input], [data-dam-select] { width: 100%; box-sizing: border-box; background: color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.08)) 45%, transparent); color: inherit; border: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.3)) 60%, transparent); border-radius: 7px; padding: 6px 8px; font: inherit; }',
169
+ '[data-dam-row] { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }',
170
+ '[data-dam-row] label { flex: 0 0 110px; opacity: .8; font-size: 12.5px; }',
171
+ '[data-dam-hint] { opacity: .5; font-size: 11.5px; margin-top: 2px; }',
172
+ '[data-dam-sidebar-btn] { display: flex; align-items: center; gap: 6px; width: 100%; border: none; background: transparent; color: inherit; cursor: pointer; padding: 6px 10px; border-radius: 8px; font: inherit; font-size: 13px; opacity: .8; }',
173
+ '[data-dam-sidebar-btn]:hover { background: color-mix(in srgb, var(--dsw-alias-label-secondary, #666) 14%, transparent); opacity: 1; }',
174
+ '[data-dam-sidebar-btn][data-active="true"] { opacity: 1; background: color-mix(in srgb, var(--dsw-alias-brand-primary, #4f7cff) 16%, transparent); color: var(--dsw-alias-brand-primary, #4f7cff); }',
175
+ '[data-dam-error] { color: var(--dsw-alias-state-error-primary, #d64545); font-size: 12px; margin-top: 6px; white-space: pre-wrap; }',
176
+ '[data-dam-muted] { opacity: .55; }',
177
+ '@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {',
178
+ ' [data-dam-panel] { background: var(--dsw-alias-bg-overlay, #ffffff); } }',
179
+ ].join('\n')
180
+ var STYLE_ID = 'dsh-auto-memory-css'
181
+ function ensureStyle() {
182
+ if (document.getElementById(STYLE_ID)) return
183
+ var tag = document.createElement('style')
184
+ tag.id = STYLE_ID
185
+ tag.dataset.plugin = '@a9i5k4/dsh-auto-memory'
186
+ tag.textContent = CSS
187
+ document.head.appendChild(tag)
188
+ }
189
+
190
+ // ───────────────────────── 通用小组件 ─────────────────────────
191
+ function useTick() { return useReducer(function (x) { return x + 1 }, 0) }
192
+
193
+ function Banner(props) {
194
+ return h('div', { 'data-dam-banner': '' }, props.children)
195
+ }
196
+
197
+ function Card(props) {
198
+ return h('div', { 'data-dam-card': '' },
199
+ h('div', { className: 'dam-date' }, props.title),
200
+ h('div', { className: 'dam-content' }, props.children))
201
+ }
202
+
203
+ function Loading() { return h('div', { 'data-dam-muted': '' }, '加载中…') }
204
+
205
+ // ───────────────────────── 侧边栏入口 ─────────────────────────
206
+ function SidebarButton() {
207
+ var tick = useTick()
208
+ useEffect(function () { return controller.subscribe(tick[1]) }, [])
209
+ return h('button', {
210
+ 'data-dam-sidebar-btn': '',
211
+ title: '记忆面板',
212
+ 'data-active': (panelOpen || panelClosing) ? 'true' : undefined,
213
+ onClick: function () { controller.toggle() },
214
+ }, h('span', null, '记忆'))
215
+ }
216
+
217
+ // ───────────────────────── 记忆面板 ─────────────────────────
218
+ function fmtSize(n) {
219
+ if (n >= 1024 * 1024) return (n / 1024 / 1024).toFixed(1) + ' MB'
220
+ if (n >= 1024) return (n / 1024).toFixed(1) + ' KB'
221
+ return n + ' B'
222
+ }
223
+
224
+ function OverviewTab() {
225
+ var statePair = useState(null)
226
+ var state = statePair[0]
227
+ var setState = statePair[1]
228
+ var detailPair = useState(false)
229
+ var showDetail = detailPair[0]
230
+ var setShowDetail = detailPair[1]
231
+ var reflectBusyPair = useState(false)
232
+ var reflectBusy = reflectBusyPair[0]
233
+ var setReflectBusy = reflectBusyPair[1]
234
+ var actMsgPair = useState('')
235
+ var actMsg = actMsgPair[0]
236
+ var setActMsg = actMsgPair[1]
237
+ useEffect(function () {
238
+ var alive = true
239
+ apiGet(API.state).then(function (s) { if (alive) setState(s) }).catch(function () {})
240
+ return function () { alive = false }
241
+ }, [])
242
+ if (!state) return h(Loading)
243
+ function oneClickReflect() {
244
+ if (reflectBusy) return
245
+ setReflectBusy(true); setActMsg('')
246
+ apiPost(API.reflectAuto, {}).then(function (d) {
247
+ setActMsg(d.result || '已生成'); setReflectBusy(false)
248
+ apiGet(API.state).then(function (s) { if (s) setState(s) }).catch(function () {})
249
+ }).catch(function (e) { setActMsg('失败: ' + e.message); setReflectBusy(false) })
250
+ }
251
+ return h('div', null,
252
+ state.pendingReflection
253
+ ? h(Banner, null, '待生成反思: ' + state.pendingReflection + ' —— 可点下方「一键反思」立即生成。')
254
+ : null,
255
+ // 状态行:今日工作 / 反思 / 笔记
256
+ h('div', { 'data-dam-kv': '' },
257
+ h('b', null, '今日工作'), h('span', null, state.todayEntries + ' 条日志'),
258
+ h('b', null, '每日反思'), h('span', null, state.latestReflectionDate || '(还没有)'),
259
+ h('b', null, '工作区'), h('span', null, state.ws)),
260
+ // 快捷操作
261
+ h('div', { 'data-dam-row': '' },
262
+ h('button', { 'data-dam-btn': '', onClick: oneClickReflect, disabled: reflectBusy }, reflectBusy ? '反思生成中…' : '一键反思'),
263
+ h('span', { 'data-dam-hint': '' }, '用最近日志自动生成反思')),
264
+ actMsg ? h('div', { 'data-dam-hint': '' }, actMsg) : null,
265
+ h('div', { 'data-dam-hint': '' },
266
+ '快捷入口:日志页签看每日记录 · 笔记页签追加项目笔记 · 接续页签接入其他 AI 记忆 · 检索页签全文搜索。'),
267
+ // 技术细节(折叠)
268
+ h('div', null,
269
+ h('button', { 'data-dam-btn': '', onClick: function () { setShowDetail(!showDetail) } }, showDetail ? '收起技术信息 ▴' : '技术信息 ▾')),
270
+ showDetail ? h('div', { 'data-dam-kv': '' },
271
+ h('b', null, '用户级记忆'), h('span', null, state.userFile + ' (' + fmtSize(state.sizes.user) + ')'),
272
+ h('b', null, '项目笔记'), h('span', null, state.notesPath + ' (' + fmtSize(state.sizes.notes) + ')'),
273
+ h('b', null, '今日日志'), h('span', null, state.logPath + ' (' + fmtSize(state.sizes.log) + ')'),
274
+ h('b', null, '配置文件'), h('span', null, state.configReadError ? ('读取失败: ' + state.configReadError) : '正常'),
275
+ h('b', null, '刷新时间'), h('span', null, state.refreshedAt ? new Date(state.refreshedAt).toLocaleString() : '尚未')) : null)
276
+ }
277
+
278
+ function LogsTab() {
279
+ var dataPair = useState(null)
280
+ var data = dataPair[0]
281
+ var setData = dataPair[1]
282
+ var openPair = useState(null)
283
+ var open = openPair[0]
284
+ var setOpen = openPair[1]
285
+ var contentPair = useState('')
286
+ var content = contentPair[0]
287
+ var setContent = contentPair[1]
288
+ useEffect(function () {
289
+ var alive = true
290
+ apiGet(API.list).then(function (d) { if (alive) setData(d) }).catch(function () {})
291
+ return function () { alive = false }
292
+ }, [])
293
+ useEffect(function () {
294
+ if (!open) return
295
+ var alive = true
296
+ apiGet(API.file, { path: open }).then(function (d) { if (alive) setContent(d.content) }).catch(function () {})
297
+ return function () { alive = false }
298
+ }, [open])
299
+ if (!data) return h(Loading)
300
+ var rows = []
301
+ if (open) {
302
+ rows.push(h(Card, { title: pathName(open) },
303
+ h('div', { 'data-dam-content': '' }, content || '(空)'),
304
+ h('button', { 'data-dam-btn': '', onClick: function () { setOpen(null); setContent('') } }, '← 返回')))
305
+ } else {
306
+ rows.push(h('div', { 'data-dam-hint': '' }, '点击日期查看当日日志(append-only):'))
307
+ for (var i = 0; i < data.logs.length; i++) {
308
+ (function (log) {
309
+ rows.push(h(Card, { title: log.date + ' · ' + fmtSize(log.size) },
310
+ h('button', { 'data-dam-btn': '', onClick: function () { setOpen(log.date + '.md') } }, '查看')))
311
+ })(data.logs[i])
312
+ }
313
+ }
314
+ return h('div', null, rows)
315
+ }
316
+ function pathName(p) { var parts = String(p).split(/[\\/]/); return parts[parts.length - 1] }
317
+
318
+ function NotesTab() {
319
+ var dataPair = useState(null)
320
+ var data = dataPair[0]
321
+ var setData = dataPair[1]
322
+ var draftPair = useState('')
323
+ var draft = draftPair[0]
324
+ var setDraft = draftPair[1]
325
+ var savingPair = useState(false)
326
+ var saving = savingPair[0]
327
+ var setSaving = savingPair[1]
328
+ var msgPair = useState('')
329
+ var msg = msgPair[0]
330
+ var setMsg = msgPair[1]
331
+ var errPair = useState('')
332
+ var err = errPair[0]
333
+ var setErr = errPair[1]
334
+ useEffect(function () {
335
+ var alive = true
336
+ apiGet(API.state).then(function (s) { if (alive) setData(s) }).catch(function () {})
337
+ return function () { alive = false }
338
+ }, [])
339
+ if (!data) return h(Loading)
340
+ function save() {
341
+ if (!draft.trim() || saving) return
342
+ setSaving(true); setMsg(''); setErr('')
343
+ apiPost(API.note, { content: draft.trim() }).then(function (d) {
344
+ setMsg(d.result || '已追加 ✓'); setDraft(''); setSaving(false)
345
+ }).catch(function (e) { setErr(e.message); setSaving(false) })
346
+ }
347
+ return h('div', null,
348
+ h('div', { 'data-dam-hint': '' }, '项目笔记: ' + data.notesPath),
349
+ h('textarea', { 'data-dam-input': '', rows: 6, placeholder: '想追加到项目笔记的内容…(保存时自动带日期标题)', value: draft, onChange: function (e) { setDraft(e.target.value) } }),
350
+ h('div', { 'data-dam-row': '' },
351
+ h('button', { 'data-dam-btn': '', onClick: save, disabled: saving }, saving ? '保存中…' : '追加'),
352
+ h('span', { 'data-dam-hint': '' }, '建议直接用对话让 agent 调 memory_note;此处为手动追加。')),
353
+ msg ? h('div', null, msg) : null,
354
+ err ? h('div', { 'data-dam-error': '' }, err) : null)
355
+ }
356
+
357
+ function ReflectionsTab() {
358
+ var dataPair = useState(null)
359
+ var data = dataPair[0]
360
+ var setData = dataPair[1]
361
+ var openPair = useState(null)
362
+ var open = openPair[0]
363
+ var setOpen = openPair[1]
364
+ var contentPair = useState('')
365
+ var content = contentPair[0]
366
+ var setContent = contentPair[1]
367
+ var busyPair = useState(false)
368
+ var busy = busyPair[0]
369
+ var setBusy = busyPair[1]
370
+ var msgPair = useState('')
371
+ var msg = msgPair[0]
372
+ var setMsg = msgPair[1]
373
+ useEffect(function () {
374
+ var alive = true
375
+ apiGet(API.list).then(function (d) { if (alive) setData(d) }).catch(function () {})
376
+ return function () { alive = false }
377
+ }, [])
378
+ useEffect(function () {
379
+ if (!open) return
380
+ var alive = true
381
+ apiGet(API.file, { path: open }).then(function (d) { if (alive) setContent(d.content) }).catch(function () {})
382
+ return function () { alive = false }
383
+ }, [open])
384
+ if (!data) return h(Loading)
385
+ function oneClickReflect() {
386
+ if (busy) return
387
+ setBusy(true); setMsg('')
388
+ apiPost(API.reflectAuto, {}).then(function (d) {
389
+ setMsg(d.result || '已生成'); setBusy(false)
390
+ apiGet(API.list).then(function (dd) { if (dd) setData(dd) }).catch(function () {})
391
+ }).catch(function (e) { setMsg('失败: ' + e.message); setBusy(false) })
392
+ }
393
+ var rows = []
394
+ if (open) {
395
+ rows.push(h(Card, { title: '反思 ' + pathName(open) }, h('div', { 'data-dam-content': '' }, content || '(空)')))
396
+ rows.push(h('button', { 'data-dam-btn': '', onClick: function () { setOpen(null); setContent('') } }, '← 返回'))
397
+ } else {
398
+ rows.push(h('div', { 'data-dam-row': '' },
399
+ h('button', { 'data-dam-btn': '', onClick: oneClickReflect, disabled: busy }, busy ? '生成中…' : '一键反思'),
400
+ h('span', { 'data-dam-hint': '' }, '自动用最近日志生成反思草稿(便于测试)')))
401
+ if (msg) rows.push(h('div', null, msg))
402
+ if (!data.reflections.length) rows.push(h('div', { 'data-dam-muted': '' }, '还没有反思。每天第一次会话时,agent 会主动呈现前一天的工作反思。'))
403
+ for (var i = 0; i < data.reflections.length; i++) {
404
+ (function (r) {
405
+ rows.push(h(Card, { title: r.date + ' · ' + fmtSize(r.size) },
406
+ h('button', { 'data-dam-btn': '', onClick: function () { setOpen('reflections/' + r.name) } }, '查看')))
407
+ })(data.reflections[i])
408
+ }
409
+ }
410
+ return h('div', null, rows)
411
+ }
412
+
413
+ function SearchTab() {
414
+ var qPair = useState('')
415
+ var q = qPair[0]
416
+ var setQ = qPair[1]
417
+ var resultPair = useState(null)
418
+ var result = resultPair[0]
419
+ var setResult = resultPair[1]
420
+ var busyPair = useState(false)
421
+ var busy = busyPair[0]
422
+ var setBusy = busyPair[1]
423
+ function search() {
424
+ if (!q.trim() || busy) return
425
+ setBusy(true); setResult(null)
426
+ apiPost(API.recall, { query: q.trim() }).then(function (d) { setResult(d.result); setBusy(false) })
427
+ .catch(function (e) { setResult('检索失败: ' + e.message); setBusy(false) })
428
+ }
429
+ return h('div', null,
430
+ h('div', { 'data-dam-row': '' },
431
+ h('input', { 'data-dam-input': '', placeholder: '搜记忆:关键词或自包含描述…', value: q, onChange: function (e) { setQ(e.target.value) }, onKeyDown: function (e) { if (e.key === 'Enter') search() } }),
432
+ h('button', { 'data-dam-btn': '', onClick: search, disabled: busy }, '检索')),
433
+ result ? h(Card, { title: '结果' }, h('div', { 'data-dam-content': '' }, result)) : null)
434
+ }
435
+
436
+ var TOOL_LABEL = { workbuddy: 'AI 助手', codebuddy: 'CodeBuddy', claude: 'Claude Code', codex: 'Codex', 'project-files': '项目文件' }
437
+ function ConnectTab() {
438
+ var sourcesPair = useState(null)
439
+ var sources = sourcesPair[0]
440
+ var setSources = sourcesPair[1]
441
+ var busyPair = useState({})
442
+ var busy = busyPair[0]
443
+ var setBusy = busyPair[1]
444
+ var msgPair = useState('')
445
+ var msg = msgPair[0]
446
+ var setMsg = msgPair[1]
447
+ var errPair = useState('')
448
+ var err = errPair[0]
449
+ var setErr = errPair[1]
450
+ function load() {
451
+ apiGet(API.external).then(function (d) { setSources(d.sources || []) }).catch(function (e) { setErr(e.message) })
452
+ }
453
+ useEffect(function () { load(); var t = setInterval(load, 60000); return function () { clearInterval(t) } }, [])
454
+ if (!sources) return err ? h('div', { 'data-dam-error': '' }, err) : h(Loading)
455
+ function doImport(source, target) {
456
+ var next = Object.assign({}, busy); next[source] = true
457
+ setBusy(next); setMsg(''); setErr('')
458
+ apiPost(API.externalImport, { source: source, target: target }).then(function (d) {
459
+ var done = Object.assign({}, busy); done[source] = false
460
+ setBusy(done); setMsg(d.result || '接入完成')
461
+ }).catch(function (e) { var done = Object.assign({}, busy); done[source] = false; setBusy(done); setErr(e.message) })
462
+ }
463
+ function importAll() {
464
+ var md = sources.filter(function (s) { return s.kind !== 'sessions' && s.enabled !== false })
465
+ setMsg('正在接入 ' + md.length + ' 个源…'); setErr('')
466
+ var chain = Promise.resolve()
467
+ md.forEach(function (s) { chain = chain.then(function () { return apiPost(API.externalImport, { source: s.id, target: 'project' }) }) })
468
+ chain.then(function () { setMsg('全部接入完成 ✓') }).catch(function (e) { setErr(e.message) })
469
+ }
470
+ var cards = []
471
+ if (!sources.length) {
472
+ cards.push(h('div', { 'data-dam-muted': '' }, '未检测到其他 AI 工具的记忆文件(CodeBuddy/Claude Code/Codex 等)。'))
473
+ }
474
+ for (var i = 0; i < sources.length; i++) {
475
+ (function (s) {
476
+ var actions = []
477
+ if (s.kind === 'sessions') {
478
+ actions.push(h('span', { 'data-dam-hint': '' }, '会话源:共 ' + s.fileCount + ' 个会话文件,用 memory_recall 按需检索'))
479
+ } else {
480
+ actions.push(h('button', { 'data-dam-btn': '', disabled: !!busy[s.id], onClick: function () { doImport(s.id, 'project') } }, busy[s.id] ? '接入中…' : '接入项目笔记'))
481
+ actions.push(h('button', { 'data-dam-btn': '', disabled: !!busy[s.id], onClick: function () { doImport(s.id, 'user') } }, '接入用户记忆'))
482
+ }
483
+ cards.push(h(Card, { title: s.name + ' · ' + s.tool + (s.enabled === false ? ' · 已停用' : '') },
484
+ h('div', { 'data-dam-hint': '' }, s.kind + ' · ' + s.fileCount + ' 个文件 · ' + fmtSize(s.size)),
485
+ s.preview ? h('div', { 'data-dam-content': '' }, s.preview) : null,
486
+ h('div', { 'data-dam-row': '' }, actions)))
487
+ })(sources[i])
488
+ }
489
+ return h('div', null,
490
+ h('div', { 'data-dam-hint': '' }, '把其他 AI 工具(CodeBuddy / Claude Code / Codex / 项目约定文件等)积累的记忆接入当前 DSH 工作。接入后内容写入本地记忆并自动标注来源,后续会随会话自动注入。'),
491
+ h('div', { 'data-dam-row': '' },
492
+ h('button', { 'data-dam-btn': '', onClick: importAll }, '一键接入全部'),
493
+ h('button', { 'data-dam-btn': '', onClick: load }, '重新扫描')),
494
+ msg ? h('div', null, msg) : null,
495
+ err ? h('div', { 'data-dam-error': '' }, err) : null,
496
+ cards)
497
+ }
498
+
499
+ // ───────────────────────── 拖动/缩放辅助 ─────────────────────────
500
+ var dragActive = false
501
+ function startPointerDrag(onMove, interactiveSel) {
502
+ return function (e) {
503
+ // 命中交互控件(按钮/输入框等)时不启动拖动
504
+ if (interactiveSel && e.target && e.target.closest && e.target.closest(interactiveSel)) return
505
+ e.preventDefault()
506
+ e.stopPropagation()
507
+ var startX = e.clientX
508
+ var startY = e.clientY
509
+ var moved = false
510
+ function move(ev) {
511
+ var dx = ev.clientX - startX
512
+ var dy = ev.clientY - startY
513
+ if (!moved && Math.abs(dx) < 2 && Math.abs(dy) < 2) return
514
+ moved = true
515
+ if (!dragActive) { dragActive = true; emit() }
516
+ onMove(dx, dy)
517
+ }
518
+ function up() {
519
+ window.removeEventListener('pointermove', move)
520
+ window.removeEventListener('pointerup', up)
521
+ if (dragActive) { dragActive = false; emit() }
522
+ controller.flushGeom()
523
+ }
524
+ window.addEventListener('pointermove', move)
525
+ window.addEventListener('pointerup', up)
526
+ }
527
+ }
528
+
529
+ function MemoryPanel() {
530
+ var tick = useTick()
531
+ var tabPair = useState('overview')
532
+ var tab = tabPair[0]
533
+ var setTab = tabPair[1]
534
+ var g = controller.geom()
535
+ useEffect(function () { return controller.subscribe(tick[1]) }, [])
536
+ if (!panelOpen && !panelClosing) return null
537
+ var body
538
+ if (tab === 'overview') body = h(OverviewTab)
539
+ else if (tab === 'logs') body = h(LogsTab)
540
+ else if (tab === 'notes') body = h(NotesTab)
541
+ else if (tab === 'reflections') body = h(ReflectionsTab)
542
+ else if (tab === 'connect') body = h(ConnectTab)
543
+ else body = h(SearchTab)
544
+ var tabs = [['overview', '概览'], ['logs', '日志'], ['notes', '笔记'], ['reflections', '反思'], ['connect', '接续'], ['search', '检索']]
545
+ var style = {
546
+ left: g.left + 'px',
547
+ top: g.top + 'px',
548
+ width: g.width + 'px',
549
+ height: g.height + 'px',
550
+ }
551
+ var dragMove = startPointerDrag(function (dx, dy) {
552
+ controller.setGeom({ left: g.left + dx, top: g.top + dy })
553
+ }, '[data-dam-btn], [data-dam-tab], [data-dam-input], [data-dam-select], textarea')
554
+ var resizeMove = startPointerDrag(function (dx, dy) {
555
+ controller.setGeom({ width: g.width + dx, height: g.height + dy })
556
+ })
557
+ return h('div', {
558
+ 'data-dam-panel': '',
559
+ style: style,
560
+ 'data-closing': panelClosing ? 'true' : undefined,
561
+ 'data-dragging': dragActive ? 'true' : undefined,
562
+ },
563
+ h('header', {
564
+ title: '拖动移动',
565
+ onPointerDown: dragMove,
566
+ },
567
+ h('strong', null, '自动记忆'),
568
+ h('span', { className: 'dam-spacer' }),
569
+ h('button', { 'data-dam-btn': '', title: '恢复默认位置', onClick: function () { controller.resetGeom() } }, '⤾'),
570
+ h('button', { 'data-dam-btn': '', title: '刷新', onClick: function () { tick[1]() } }, '⟳'),
571
+ h('button', { 'data-dam-btn': '', title: '关闭', onClick: function () { controller.close() } }, '✕')),
572
+ h('div', { 'data-dam-tabs': '' }, tabs.map(function (t) {
573
+ return h('button', { key: t[0], 'data-dam-tab': '', 'data-active': tab === t[0] ? 'true' : undefined, onClick: function () { setTab(t[0]) } }, t[1])
574
+ })),
575
+ h('div', { 'data-dam-body': '' }, body),
576
+ h('div', { 'data-dam-resize': '', title: '拖动调整大小', onPointerDown: resizeMove }))
577
+ }
578
+
579
+ // ───────────────────────── 设置页 ─────────────────────────
580
+ var STYLE_OPTIONS = [['auto', '由内容决定'], ['life', '生活化'], ['professional', '专业性']]
581
+ function SettingsPage() {
582
+ var cfgPair = useState(null)
583
+ var cfg = cfgPair[0]
584
+ var setCfg = cfgPair[1]
585
+ var busyPair = useState(false)
586
+ var busy = busyPair[0]
587
+ var setBusy = busyPair[1]
588
+ var msgPair = useState('')
589
+ var msg = msgPair[0]
590
+ var setMsg = msgPair[1]
591
+ var errPair = useState('')
592
+ var err = errPair[0]
593
+ var setErr = errPair[1]
594
+ useEffect(function () {
595
+ var alive = true
596
+ apiGet(API.config).then(function (d) { if (alive) setCfg(d.config) }).catch(function (e) { setErr(e.message) })
597
+ return function () { alive = false }
598
+ }, [])
599
+ if (!cfg) return err ? h('div', { 'data-dam-error': '' }, err) : h(Loading)
600
+ function set(key, value) { var next = Object.assign({}, cfg); next[key] = value; setCfg(next) }
601
+ function save() {
602
+ if (busy) return
603
+ setBusy(true); setMsg(''); setErr('')
604
+ apiPost(API.config, cfg).then(function (d) { setCfg(d.config); setMsg('已保存 ✓'); setBusy(false) })
605
+ .catch(function (e) { setErr(e.message); setBusy(false) })
606
+ }
607
+ function field(label, control, hint) {
608
+ return h('div', null,
609
+ h('div', { 'data-dam-row': '' }, h('label', null, label), control),
610
+ hint ? h('div', { 'data-dam-hint': '' }, hint) : null)
611
+ }
612
+ return h('div', null,
613
+ h('div', { 'data-dam-hint': '' }, '记忆存储与行为设置(保存到 DSH 主目录 dsh-auto-memory.json):'),
614
+ field('用户记忆目录', h('input', { 'data-dam-input': '', value: cfg.userMemoryDir, onChange: function (e) { set('userMemoryDir', e.target.value) } }), '跨项目规则存放处,支持 ~ 开头;需有文件写权限。'),
615
+ field('项目记忆目录', h('input', { 'data-dam-input': '', value: cfg.projectMemoryDir, onChange: function (e) { set('projectMemoryDir', e.target.value) } }), '相对各工作区的目录名(默认 .dsh-memory)。'),
616
+ field('注入记忆上下文', h('input', { type: 'checkbox', checked: !!cfg.injectEnabled, onChange: function (e) { set('injectEnabled', e.target.checked) } }), '每次组装提示词时自动注入 <memory_system> 块。'),
617
+ field('注入预算(字符)', h('input', { 'data-dam-input': '', type: 'number', min: 400, value: cfg.injectBudgetChars, onChange: function (e) { set('injectBudgetChars', Number(e.target.value) || 2400) } }), '记忆块总预算,超出部分截断。'),
618
+ field('注入最近日志天数', h('input', { 'data-dam-input': '', type: 'number', min: 1, max: 14, value: cfg.recentDaysInjected, onChange: function (e) { set('recentDaysInjected', Number(e.target.value) || 3) } }), '会话开始时注入最近 N 天的工作日志尾部。'),
619
+ field('每日反思', h('input', { type: 'checkbox', checked: !!cfg.reflectEnabled, onChange: function (e) { set('reflectEnabled', e.target.checked) } }), '昨天有工作日志时,会话首轮主动呈现昨日反思。'),
620
+ field('反思风格', h('select', { 'data-dam-select': '', value: cfg.reflectStyle, onChange: function (e) { set('reflectStyle', e.target.value) } },
621
+ STYLE_OPTIONS.map(function (o) { return h('option', { key: o[0], value: o[0] }, o[1]) })), '生活化 / 专业性 / 由内容决定。'),
622
+ h('div', { 'data-dam-row': '' },
623
+ h('button', { 'data-dam-btn': '', onClick: save, disabled: busy }, busy ? '保存中…' : '保存设置'),
624
+ msg ? h('span', null, msg) : null),
625
+ err ? h('div', { 'data-dam-error': '' }, err) : null)
626
+ }
627
+
628
+ // ───────────────────────── 插件挂载 ─────────────────────────
629
+ function apply(ctx) {
630
+ try { ensureStyle() } catch (e) { console.warn('[dsh-auto-memory] style inject failed', e) }
631
+ ctx.effect(function () {
632
+ return function () {
633
+ var tag = document.getElementById(STYLE_ID)
634
+ if (tag) tag.remove()
635
+ if (closeTimer) { clearTimeout(closeTimer); closeTimer = null }
636
+ }
637
+ }, 'dsh-auto-memory: styles')
638
+
639
+ var slots = ctx.slots
640
+ if (!slots) { console.warn('[dsh-auto-memory] slots service unavailable'); return }
641
+
642
+ try {
643
+ slots.inject('sidebar.footer.action', function () {
644
+ return slots.register({ name: 'sidebar.footer.action', id: 'auto-memory', order: 5, label: '记忆' }, function () { return h(SidebarButton) })
645
+ })
646
+ slots.inject('shell.overlay', function () {
647
+ return slots.register({ name: 'shell.overlay', id: 'auto-memory', order: 5 }, function () { return h(MemoryPanel) })
648
+ })
649
+ slots.inject('settings.section', function () {
650
+ return slots.register({ name: 'settings.section', id: 'auto-memory', order: 25, label: '自动记忆' }, function (props) { return h(SettingsPage, { close: props && props.close }) })
651
+ })
652
+ } catch (e) {
653
+ console.warn('[dsh-auto-memory] slot registration failed', e)
654
+ }
655
+ console.log('[dsh-auto-memory] client ready: sidebar entry + panel + settings page')
656
+ }
657
+
658
+ exports.inject = ['slots']
659
+ exports.apply = apply
660
+ return module.exports
661
+ },
662
+ })