@a9i5k4/dsh-auto-memory 0.1.7 → 0.1.9

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 CHANGED
@@ -7,8 +7,9 @@
7
7
  * 3. settings.section — 自动记忆设置页(存储位置、注入、反思风格)
8
8
  * Data flows over /api/dsh-auto-memory/* (loopback-only host routes).
9
9
  */
10
+ console.log('[dsh-auto-memory] client v0.1.9 fingerprint: drawer-summary-ok')
10
11
  window.__ModuleLoader__.load({
11
- id: '@a9i5k4/dsh-auto-memory',
12
+ id: '@deepseek-ai/dsh-auto-memory',
12
13
  factory: (require) => {
13
14
  var module = { exports: {} }
14
15
  var exports = module.exports
@@ -97,12 +98,22 @@ window.__ModuleLoader__.load({
97
98
  memoryPanel: '记忆面板',
98
99
  memory: '记忆',
99
100
  autoMemory: '自动记忆',
100
- overview: '概览', logs: '日志', notes: '笔记', reflections: '反思', connect: '接续', search: '检索',
101
- refresh: '刷新', close: '关闭', dragMove: '拖动移动', dragResize: '拖动调整大小', resetPos: '恢复默认位置',
101
+ overview: '概览', logs: '日志', notes: '笔记', reflections: '反思', connect: '接续', calendar: '日历', search: '检索', workspaces: '工作区',
102
+ wsGenerating: '正在生成各工作区总结(每个工作区一次 AI 调用,可能稍慢)…', wsNone: '未发现带记忆的工作区', wsNoSummary: '(无总结)', wsOverview: '工作区总结(跨工作区)',
103
+ debugCenter: '调试中心', dbgLoading: '加载诊断信息…', dbgRefresh: '刷新', dbgRefreshing: '刷新中…',
104
+ fMemoryRoot: '记忆根目录', fMemoryRootHint: '集中式存储:所有工作区记忆统一放在此目录下(每工作区一个子目录),旧版分散的记忆会自动迁移。',
105
+ fBrowse: '浏览…', browseTitle: '选择记忆根目录', fUp: '上级', fSelectDir: '选择此目录',
106
+ pickedDir: '已选择目录:', rememberSave: '(点击「保存设置」后生效)', pickerUnavailable: '系统文件夹选择器不可用,改用内嵌浏览。',
107
+ fDayBoundary: '日界(分钟)', fDayBoundaryHint: '从 0 点起算:凌晨在此之前的活儿归前一天。默认 450=早上 7:30 进入新一天;改 480=8:00;0=按午夜切日。',
108
+ refresh: '刷新', refreshing: '刷新中…', close: '关闭', dragMove: '拖动移动', dragResize: '拖动调整大小', resetPos: '恢复默认位置',
102
109
  generated: '已生成', failed: '失败: ',
110
+ generatedAt: '生成于 ',
111
+ pointsCount: ' 项', summarizing: 'AI 总结生成中…', summaryFailed: 'AI 总结生成失败,点 ⟳ 重试',
103
112
  pendingReflection: '待生成反思: ',
104
113
  pendingReflectionHint: ' —— 可点下方「一键反思」立即生成。',
105
114
  todayWork: '今日工作', logEntries: ' 条日志',
115
+ autoSettledToday: '今日已自动沉淀 ', autoSettledSuffix: ' 条要点',
116
+ autoSettledRecent: '最近: ', autoSettledNone: '本轮对话暂无自动沉淀',
106
117
  dailyReflection: '每日反思', notYet: '(还没有)',
107
118
  workspace: '工作区',
108
119
  reflecting: '反思生成中…', oneClickReflect: '一键反思',
@@ -121,6 +132,7 @@ window.__ModuleLoader__.load({
121
132
  reflectAutoHint: '自动用最近日志生成反思草稿(便于测试)',
122
133
  noReflection: '还没有反思。每天第一次会话时,agent 会主动呈现前一天的工作反思。',
123
134
  searchFailed: '检索失败: ', searchPlaceholder: '搜记忆:关键词或自包含描述…', searchBtn: '检索', resultTitle: '结果',
135
+ smartSearch: '智能检索', smartAnswer: '智能回答', keywordsLabel: '关键词: ',
124
136
  projectFiles: '项目文件', aiAssistant: 'AI 助手',
125
137
  imported: '接入完成', importing: '正在接入 ', importingSuffix: ' 个源…', allImported: '全部接入完成',
126
138
  noExternal: '未检测到其他 AI 工具的记忆文件(CodeBuddy/Claude Code/Codex 等)。',
@@ -134,6 +146,7 @@ window.__ModuleLoader__.load({
134
146
  yesterdayTimeline: '昨天(', pendingReflectionShort: '昨天的工作还没复盘(',
135
147
  saved: '已保存',
136
148
  settingsHeader: '记忆存储与行为设置(保存到 DSH 主目录 dsh-auto-memory.json):',
149
+ fFontSize: '界面字号', fFontSizeHint: '记忆面板文字大小(立即生效,仅本机)', fsSm: '小', fsMd: '标准', fsLg: '大', fsXl: '特大',
137
150
  fUserDir: '用户记忆目录', fUserDirHint: '跨项目规则存放处,支持 ~ 开头;需有文件写权限。',
138
151
  fProjectDir: '项目记忆目录', fProjectDirHint: '相对各工作区的目录名(默认 .dsh-memory)。',
139
152
  fInject: '注入记忆上下文', fInjectHint: '每次组装提示词时自动注入 <memory_system> 块。',
@@ -158,12 +171,22 @@ window.__ModuleLoader__.load({
158
171
  memoryPanel: 'Memory Panel',
159
172
  memory: 'Memory',
160
173
  autoMemory: 'Auto Memory',
161
- overview: 'Overview', logs: 'Logs', notes: 'Notes', reflections: 'Reflections', connect: 'Connect', search: 'Search',
162
- refresh: 'Refresh', close: 'Close', dragMove: 'Drag to move', dragResize: 'Drag to resize', resetPos: 'Reset position',
174
+ overview: 'Overview', logs: 'Logs', notes: 'Notes', reflections: 'Reflections', connect: 'Connect', calendar: 'Calendar', search: 'Search', workspaces: 'Workspaces',
175
+ wsGenerating: 'Generating per-workspace summaries (one AI call each, may take a while)…', wsNone: 'No workspace with memory found', wsNoSummary: '(no summary)', wsOverview: 'Workspace summaries (all workspaces)',
176
+ debugCenter: 'Debug Center', dbgLoading: 'Loading diagnostics…', dbgRefresh: 'Refresh', dbgRefreshing: 'Refreshing…',
177
+ fMemoryRoot: 'Memory root', fMemoryRootHint: 'Centralized storage: all workspace memories live here (one subdir per workspace); legacy memories are auto-migrated.',
178
+ fBrowse: 'Browse…', browseTitle: 'Choose memory root', fUp: 'Up', fSelectDir: 'Select this folder',
179
+ pickedDir: 'Selected folder:', rememberSave: '(click Save to apply)', pickerUnavailable: 'Native folder picker unavailable, falling back to in-app browser.',
180
+ fDayBoundary: 'Day boundary (minutes)', fDayBoundaryHint: 'Minutes from midnight: work before it counts as the previous day. Default 450 = 7:30 AM starts the new day; 480 = 8:00; 0 = midnight cut.',
181
+ refresh: 'Refresh', refreshing: 'Refreshing…', close: 'Close', dragMove: 'Drag to move', dragResize: 'Drag to resize', resetPos: 'Reset position',
163
182
  generated: 'Generated', failed: 'Failed: ',
183
+ generatedAt: 'Generated ',
184
+ pointsCount: ' items', summarizing: 'AI summary generating…', summaryFailed: 'Summary failed, press refresh to retry',
164
185
  pendingReflection: 'Pending reflection: ',
165
186
  pendingReflectionHint: ' — click "One-click Reflect" below to generate now.',
166
187
  todayWork: 'Today', logEntries: ' log entries',
188
+ autoSettledToday: 'Auto-consolidated ', autoSettledSuffix: ' points today',
189
+ autoSettledRecent: 'Latest: ', autoSettledNone: 'Nothing auto-consolidated this turn',
167
190
  dailyReflection: 'Daily reflection', notYet: '(none yet)',
168
191
  workspace: 'Workspace',
169
192
  reflecting: 'Generating…', oneClickReflect: 'One-click Reflect',
@@ -182,6 +205,7 @@ window.__ModuleLoader__.load({
182
205
  reflectAutoHint: 'Auto-generate a reflection draft from recent logs (for testing)',
183
206
  noReflection: "No reflections yet. On the first session each day, the agent presents the previous day's reflection.",
184
207
  searchFailed: 'Search failed: ', searchPlaceholder: 'Search memory: keyword or self-contained description…', searchBtn: 'Search', resultTitle: 'Results',
208
+ smartSearch: 'Smart search', smartAnswer: 'AI Answer', keywordsLabel: 'Keywords: ',
185
209
  projectFiles: 'Project files', aiAssistant: 'AI Assistant',
186
210
  imported: 'Imported', importing: 'Importing ', importingSuffix: ' sources…', allImported: 'All imported',
187
211
  noExternal: 'No memory files found from other AI tools (CodeBuddy/Claude Code/Codex etc.).',
@@ -195,6 +219,7 @@ window.__ModuleLoader__.load({
195
219
  yesterdayTimeline: 'Yesterday (', pendingReflectionShort: 'Yesterday\'s work not reviewed (',
196
220
  saved: 'Saved',
197
221
  settingsHeader: 'Memory storage & behavior (saved to ~/.dsh/dsh-auto-memory.json):',
222
+ fFontSize: 'Panel font size', fFontSizeHint: 'Memory panel text size (applies immediately, this device only)', fsSm: 'Small', fsMd: 'Normal', fsLg: 'Large', fsXl: 'Extra large',
198
223
  fUserDir: 'User memory dir', fUserDirHint: 'Cross-project rules; supports ~ prefix; needs write permission.',
199
224
  fProjectDir: 'Project memory dir', fProjectDirHint: 'Directory name relative to each workspace (default .dsh-memory).',
200
225
  fInject: 'Inject memory context', fInjectHint: 'Auto-inject <memory_system> block into every prompt.',
@@ -216,6 +241,21 @@ window.__ModuleLoader__.load({
216
241
  }
217
242
  }
218
243
  var locale = 'zh'
244
+ var sessions = null
245
+ // 当前会话工作区(跟随 GUI 切换工作区):从 sessions 服务拿,供 state/summarize/greet 请求使用
246
+ function currentWs() {
247
+ try {
248
+ var snap = sessions && sessions.list && sessions.list.getSnapshot()
249
+ if (!snap) return ''
250
+ var id = snap.current
251
+ if (id === undefined || id === null) return ''
252
+ var s = snap.byId && snap.byId[id]
253
+ return (s && typeof s.cwd === 'string') ? s.cwd : ''
254
+ } catch (e) { return '' }
255
+ }
256
+ var fontScale = 'lg'
257
+ var FONT_SCALES = { sm: '小', md: '标准', lg: '大', xl: '特大' }
258
+ var FONT_SCALE_VALUES = { sm: '0.9', md: '1', lg: '1.15', xl: '1.3' }
219
259
  var localeListeners = new Set()
220
260
  function t(key) { return (I18N[locale] && I18N[locale][key]) || I18N.zh[key] || key }
221
261
  function setLocale(l) { if (l !== 'zh' && l !== 'en') return; if (l === locale) return; locale = l; localeListeners.forEach(function (fn) { try { fn(l) } catch (e) {} }) }
@@ -227,12 +267,19 @@ window.__ModuleLoader__.load({
227
267
  list: '/api/dsh-auto-memory/list',
228
268
  file: '/api/dsh-auto-memory/file',
229
269
  recall: '/api/dsh-auto-memory/recall',
270
+ smartRecall: '/api/dsh-auto-memory/smart-recall',
271
+ workspaces: '/api/dsh-auto-memory/workspaces',
272
+ debug: '/api/dsh-auto-memory/debug',
273
+ browseDir: '/api/dsh-auto-memory/browse-dir',
274
+ pickDir: '/api/dsh-auto-memory/pick-dir',
230
275
  config: '/api/dsh-auto-memory/config',
231
276
  reflect: '/api/dsh-auto-memory/reflect',
232
277
  reflectAuto: '/api/dsh-auto-memory/reflect-auto',
233
278
  note: '/api/dsh-auto-memory/note',
234
279
  external: '/api/dsh-auto-memory/external',
235
280
  externalImport: '/api/dsh-auto-memory/external-import',
281
+ summarize: '/api/dsh-auto-memory/summarize',
282
+ greet: '/api/dsh-auto-memory/greet',
236
283
  }
237
284
  function query(params) {
238
285
  var search = new URLSearchParams()
@@ -257,7 +304,7 @@ window.__ModuleLoader__.load({
257
304
  // 视觉:液态玻璃(毛玻璃)—— backdrop-filter + DSH 主题令牌(--dsw-alias-*),
258
305
  // 跟随亮/暗主题与页面背景自适应;位置/尺寸由 JS 几何状态驱动(可拖动、可缩放)。
259
306
  var CSS = [
260
- '[data-dam-panel] { position: fixed; left: 16px; width: 440px; height: 560px;',
307
+ '[data-dam-panel] { position: fixed; left: 16px; width: 440px; height: 560px; --dam-scale: 1;',
261
308
  ' max-width: calc(100vw - 32px); max-height: calc(100vh - 32px);',
262
309
  ' display: flex; flex-direction: column; overflow: hidden; z-index: 3000; pointer-events: auto;',
263
310
  ' border-radius: 16px; font: 13px/1.55 system-ui, "Segoe UI", sans-serif;',
@@ -270,36 +317,39 @@ window.__ModuleLoader__.load({
270
317
  '@keyframes dam-in { from { opacity: 0; transform: scale(.92) translateY(10px); } to { opacity: 1; transform: none; } }',
271
318
  '@keyframes dam-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: scale(.95) translateY(6px); } }',
272
319
  '[data-dam-panel][data-closing="true"] { animation: dam-out .16s ease-in both; }',
320
+ '[data-dam-panel][data-scale="sm"] { --dam-scale: .9; }',
321
+ '[data-dam-panel][data-scale="lg"] { --dam-scale: 1.15; }',
322
+ '[data-dam-panel][data-scale="xl"] { --dam-scale: 1.3; }',
273
323
  '[data-dam-panel]::before { content: ""; position: absolute; inset: 0 0 auto 0; height: 64%; pointer-events: none;',
274
324
  ' background: linear-gradient(180deg, rgba(255,255,255,.13), rgba(255,255,255,0) 70%); border-radius: 16px 16px 0 0; }',
275
325
  '[data-dam-panel][data-dragging="true"] { user-select: none; }',
276
326
  '[data-dam-panel] header { display: flex; align-items: center; gap: 8px; padding: 10px 14px; cursor: grab;',
277
327
  ' border-bottom: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.25)) 55%, transparent); }',
278
328
  '[data-dam-panel][data-dragging="true"] header { cursor: grabbing; }',
279
- '[data-dam-panel] header strong { font-size: 14px; }',
329
+ '[data-dam-panel] header strong { font-size: calc(14px * var(--dam-scale)); }',
280
330
  '[data-dam-panel] header .dam-spacer { flex: 1; }',
281
331
  '[data-dam-resize] { position: absolute; right: 0; bottom: 0; width: 22px; height: 22px; cursor: nwse-resize;',
282
332
  ' opacity: .55; z-index: 2; border-radius: 0 0 16px 0;',
283
333
  ' background: linear-gradient(135deg, transparent 50%, color-mix(in srgb, var(--dsw-alias-label-secondary, #666) 45%, transparent) 50%); }',
284
334
  '[data-dam-resize]:hover { opacity: 1; }',
285
- '[data-dam-btn] { border: none; background: transparent; cursor: pointer; color: inherit; opacity: .75; font-size: 13px; padding: 4px 8px; border-radius: 6px; }',
335
+ '[data-dam-btn] { border: none; background: transparent; cursor: pointer; color: inherit; opacity: .75; font-size: calc(13px * var(--dam-scale)); padding: 4px 8px; border-radius: 6px; }',
286
336
  '[data-dam-btn]:hover { opacity: 1; background: color-mix(in srgb, var(--dsw-alias-label-secondary, #666) 16%, transparent); }',
287
337
  '[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); }',
288
- '[data-dam-tab] { border: none; background: transparent; cursor: pointer; color: inherit; opacity: .6; padding: 5px 10px; border-radius: 7px; font-size: 12.5px; }',
338
+ '[data-dam-tab] { border: none; background: transparent; cursor: pointer; color: inherit; opacity: .6; padding: 5px 10px; border-radius: 7px; font-size: calc(12.5px * var(--dam-scale)); }',
289
339
  '[data-dam-tab][data-active="true"] { opacity: 1; background: color-mix(in srgb, var(--dsw-alias-label-secondary, #666) 18%, transparent); font-weight: 600; }',
290
340
  '[data-dam-body] { flex: 1; overflow: auto; padding: 12px 14px; }',
291
- '[data-dam-kv] { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 12.5px; }',
341
+ '[data-dam-kv] { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: calc(12.5px * var(--dam-scale)); }',
292
342
  '[data-dam-kv] b { opacity: .55; font-weight: 500; }',
293
- '[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;',
343
+ '[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: calc(12.5px * var(--dam-scale));',
294
344
  ' background: color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.06)) 40%, transparent); }',
295
345
  '[data-dam-card] .dam-date { font-weight: 600; margin-bottom: 4px; }',
296
346
  '[data-dam-card] .dam-content { white-space: pre-wrap; word-break: break-word; max-height: 220px; overflow: auto; }',
297
347
  '[data-dam-banner] { border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary, #e6a23c) 55%, transparent);',
298
- ' 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; }',
348
+ ' 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: calc(12.5px * var(--dam-scale)); }',
299
349
  '[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; }',
300
350
  '[data-dam-row] { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }',
301
- '[data-dam-row] label { flex: 0 0 110px; opacity: .8; font-size: 12.5px; }',
302
- '[data-dam-hint] { opacity: .5; font-size: 11.5px; margin-top: 2px; }',
351
+ '[data-dam-row] label { flex: 0 0 110px; opacity: .8; font-size: calc(12.5px * var(--dam-scale)); }',
352
+ '[data-dam-hint] { opacity: .5; font-size: calc(11.5px * var(--dam-scale)); margin-top: 2px; }',
303
353
  '[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; }',
304
354
  '[data-dam-sidebar-btn]:hover { background: color-mix(in srgb, var(--dsw-alias-label-secondary, #666) 14%, transparent); opacity: 1; }',
305
355
  '[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); }',
@@ -313,7 +363,7 @@ window.__ModuleLoader__.load({
313
363
  if (document.getElementById(STYLE_ID)) return
314
364
  var tag = document.createElement('style')
315
365
  tag.id = STYLE_ID
316
- tag.dataset.plugin = '@a9i5k4/dsh-auto-memory'
366
+ tag.dataset.plugin = '@deepseek-ai/dsh-auto-memory'
317
367
  tag.textContent = CSS
318
368
  document.head.appendChild(tag)
319
369
  }
@@ -359,6 +409,9 @@ window.__ModuleLoader__.load({
359
409
  var openPair = useState({})
360
410
  var openMap = openPair[0]
361
411
  var setOpenMap = openPair[1]
412
+ var subOpenPair = useState({})
413
+ var subOpenMap = subOpenPair[0]
414
+ var setSubOpenMap = subOpenPair[1]
362
415
  // 智能时段判定
363
416
  var hour = new Date().getHours()
364
417
  var seg = hour < 9 ? 'morning' : hour < 12 ? 'forenoon' : hour < 14 ? 'noon' : hour < 18 ? 'afternoon' : 'evening'
@@ -368,7 +421,7 @@ window.__ModuleLoader__.load({
368
421
  var curSegName = { morning: '早晨', forenoon: '上午', noon: '中午', afternoon: '下午', evening: '晚上' }[seg]
369
422
  // 昨天抽屉(早晨显示)
370
423
  if (seg === 'morning' && g.entries.length) {
371
- drawers.push({ key: 'yesterday', label: t('yesterdayDrawer') + (g.yesterdayDate || ''), hint: '', items: g.entries, withTime: true, defaultOpen: true })
424
+ drawers.push({ key: 'yesterday', label: t('yesterdayDrawer') + (g.yesterdayDate || ''), hint: '', items: g.entries, withTime: true, defaultOpen: true, period: '昨天' })
372
425
  }
373
426
  // 今天各时段抽屉(已过的时段)
374
427
  var seenSeg = { '早晨': seg !== 'morning', '上午': (seg === 'forenoon' || seg === 'noon' || seg === 'afternoon' || seg === 'evening'), '中午': (seg === 'noon' || seg === 'afternoon' || seg === 'evening'), '下午': (seg === 'afternoon' || seg === 'evening'), '晚上': seg === 'evening' }
@@ -378,7 +431,7 @@ window.__ModuleLoader__.load({
378
431
  if (!seenSeg[sname]) continue
379
432
  var items = (ps && ps.groups && ps.groups[sname]) || []
380
433
  if (!items.length) continue
381
- drawers.push({ key: 'seg-' + sname, label: t('segPrefix') + sname, hint: '', items: items.map(function (x) { return { time: '', text: x } }), withTime: false, defaultOpen: sname === curSegName })
434
+ drawers.push({ key: 'seg-' + sname, label: t('segPrefix') + sname, hint: '', items: items.map(function (x) { return { time: '', text: x } }), withTime: false, defaultOpen: sname === curSegName, period: sname })
382
435
  }
383
436
  // 外层:生活化问候
384
437
  var totalCount = 0
@@ -399,24 +452,68 @@ window.__ModuleLoader__.load({
399
452
  }
400
453
  var rows = []
401
454
  rows.push(h('div', { 'data-dam-content': '' }, greetLine))
402
- // 抽屉列表
455
+ // 抽屉列表(AI 生活化总结缓存)
456
+ var sumCachePair = useState({})
457
+ var sumCache = sumCachePair[0]
458
+ var setSumCache = sumCachePair[1]
459
+ function requestSummary(key, period, force) {
460
+ if (!force && sumCache[key] !== undefined) return
461
+ apiPost(API.summarize, { period: period, force: !!force, ws: currentWs() }).then(function (d) {
462
+ if (d && (d.summary || (d.works && d.works.length))) {
463
+ var n = Object.assign({}, sumCache)
464
+ n[key] = { summary: d.summary || d.result || '', works: d.works || [], at: d.generatedAt || Date.now(), failed: false }
465
+ setSumCache(n)
466
+ }
467
+ }).catch(function () {
468
+ var n = Object.assign({}, sumCache)
469
+ n[key] = { summary: '', works: [], at: 0, failed: true }
470
+ setSumCache(n)
471
+ })
472
+ }
473
+ // 面板刷新键(nonce 变化):已缓存的展开抽屉 force 重新生成
474
+ useEffect(function () {
475
+ if (!props.nonce) return
476
+ for (var d = 0; d < drawers.length; d++) {
477
+ var dr = drawers[d]
478
+ var isOpen = openMap[dr.key] !== undefined ? openMap[dr.key] : dr.defaultOpen
479
+ if (isOpen && sumCache[dr.key] !== undefined) requestSummary(dr.key, dr.period, true)
480
+ }
481
+ }, [props.nonce])
403
482
  for (var d = 0; d < drawers.length; d++) {
404
483
  (function (dr) {
405
484
  var isOpen = openMap[dr.key] !== undefined ? openMap[dr.key] : dr.defaultOpen
485
+ if (isOpen && sumCache[dr.key] === undefined) requestSummary(dr.key, dr.period, false)
486
+ var cached = sumCache[dr.key]
487
+ // 大抽屉标题:有 AI 总结时用总结内容(用户要求),否则用时段名
488
+ var bigTitle = cached && cached.summary ? cached.summary : dr.label
406
489
  rows.push(h('div', { key: dr.key, style: { marginTop: '8px' } },
407
490
  h('button', {
408
491
  'data-dam-btn': '',
409
492
  onClick: function () { var n = Object.assign({}, openMap); n[dr.key] = !isOpen; setOpenMap(n) },
410
- style: { width: '100%', display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '5px 8px', borderRadius: '7px', background: 'color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.08)) 45%, transparent)', border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.3)) 50%, transparent)' },
493
+ style: { width: '100%', textAlign: 'left', padding: '7px 9px', borderRadius: '8px', background: 'color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.08)) 45%, transparent)', border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.3)) 50%, transparent)' },
411
494
  },
412
- h('span', { style: { fontWeight: 600, fontSize: '12.5px' } }, (isOpen ? '▼ ' : '▶ ') + dr.label),
413
- h('span', { 'data-dam-muted': '', style: { fontSize: '11.5px' } }, dr.items.length + t('logEntries'))),
414
- isOpen ? h('div', { style: { padding: '6px 4px 2px 10px' } },
415
- dr.items.map(function (en) {
416
- return h('div', { key: (en.time || '') + en.text, style: { marginBottom: '3px', fontSize: '12.5px', lineHeight: 1.5 } },
417
- dr.withTime && en.time ? h('span', { 'data-dam-muted': '', style: { fontFamily: 'monospace', fontSize: '11px', marginRight: '6px' } }, en.time) : null,
418
- h('span', { style: { wordBreak: 'break-word' } }, en.text))
419
- })) : null))
495
+ h('div', { style: { fontWeight: 600, fontSize: 'calc(12.5px * var(--dam-scale))', lineHeight: 1.5 } }, (isOpen ? '▼ ' : '▶ ') + bigTitle),
496
+ h('div', { 'data-dam-muted': '', style: { fontSize: 'calc(11px * var(--dam-scale))', marginTop: '2px' } },
497
+ dr.label + ' · ' + dr.items.length + t('logEntries') + (cached && cached.at ? ' · ' + t('generatedAt') + new Date(cached.at).toLocaleTimeString() : ''))),
498
+ isOpen ? h('div', { style: { padding: '4px 2px 2px 8px' } },
499
+ cached && cached.works && cached.works.length ? cached.works.map(function (w, wi) {
500
+ var sk = dr.key + ':w' + wi
501
+ var sOpen = subOpenMap[sk] !== undefined ? subOpenMap[sk] : false
502
+ return h('div', { key: sk, style: { marginTop: '5px' } },
503
+ h('button', {
504
+ 'data-dam-btn': '',
505
+ onClick: function () { var n = Object.assign({}, subOpenMap); n[sk] = !sOpen; setSubOpenMap(n) },
506
+ style: { width: '100%', textAlign: 'left', padding: '4px 8px', borderRadius: '6px', background: 'color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.06)) 40%, transparent)', border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.22)) 45%, transparent)' },
507
+ },
508
+ h('span', { style: { fontWeight: 600, fontSize: 'calc(12px * var(--dam-scale))' } }, (sOpen ? '▾ ' : '▸ ') + w.title),
509
+ h('span', { 'data-dam-muted': '', style: { fontSize: 'calc(11px * var(--dam-scale))', marginLeft: '6px' } }, w.points.length + t('pointsCount'))),
510
+ sOpen ? h('div', { style: { padding: '4px 4px 2px 14px' } },
511
+ w.points.map(function (pt, pi) {
512
+ return h('div', { key: pi, style: { fontSize: 'calc(12px * var(--dam-scale))', lineHeight: 1.5, marginBottom: '2px' } }, '· ' + pt)
513
+ })) : null)
514
+ }) : cached && cached.failed ? h('div', { 'data-dam-hint': '', style: { padding: '4px 6px' } }, t('summaryFailed'))
515
+ : cached && cached.summary ? h('div', { 'data-dam-hint': '', style: { padding: '4px 6px' } }, cached.summary)
516
+ : h('div', { 'data-dam-hint': '', style: { padding: '4px 6px' } }, t('summarizing'))) : null))
420
517
  })(drawers[d])
421
518
  }
422
519
  // 待反思提醒
@@ -426,7 +523,7 @@ window.__ModuleLoader__.load({
426
523
  return h(Card, { title: (g.period || '') + ' · ' + segLabel }, rows)
427
524
  }
428
525
 
429
- function OverviewTab() {
526
+ function OverviewTab(props) {
430
527
  var statePair = useState(null)
431
528
  var state = statePair[0]
432
529
  var setState = statePair[1]
@@ -439,10 +536,35 @@ window.__ModuleLoader__.load({
439
536
  var actMsgPair = useState('')
440
537
  var actMsg = actMsgPair[0]
441
538
  var setActMsg = actMsgPair[1]
539
+ var wsPair = useState(null)
540
+ var wsData = wsPair[0]
541
+ var setWsData = wsPair[1]
542
+ // 每次进入面板 / 点刷新(nonce 变化)/ 面板打开期间每 30s 自动重拉,保证自动沉淀等数据新鲜
442
543
  useEffect(function () {
443
544
  var alive = true
444
- apiGet(API.state).then(function (s) { if (alive) setState(s) }).catch(function () {})
545
+ // 跨工作区总结(概览页集成:所有工作区的小总结+细分;读缓存)
546
+ apiPost(API.workspaces, { force: false }).then(function (d) { if (d && alive) setWsData(d) }).catch(function () {})
547
+ apiGet(API.state, { ws: currentWs() }).then(function (s) {
548
+ if (alive) setState(s)
549
+ // 无 AI 问候 → 自动生成一次(host 按时段缓存,不会重复生成)
550
+ if (s && s.greeting && !s.greeting.greeting) {
551
+ apiPost(API.greet, { ws: currentWs() }).then(function (d) {
552
+ if (d && d.greeting && alive) {
553
+ setState(function (prev) {
554
+ var g0 = (prev && prev.greeting) || {}
555
+ return Object.assign({}, prev, { greeting: Object.assign({}, g0, { greeting: d.greeting, hasGreeting: true }) })
556
+ })
557
+ }
558
+ }).catch(function () {})
559
+ }
560
+ }).catch(function () {})
445
561
  return function () { alive = false }
562
+ }, [props && props.nonce])
563
+ useEffect(function () {
564
+ var timer = setInterval(function () {
565
+ apiGet(API.state, { ws: currentWs() }).then(function (s) { setState(s) }).catch(function () {})
566
+ }, 30000)
567
+ return function () { clearInterval(timer) }
446
568
  }, [])
447
569
  useEffect(function () {
448
570
  // 记录本次活动时间;下次打开若相隔>1小时,GreetingCard 显示"欢迎回来"
@@ -454,12 +576,25 @@ window.__ModuleLoader__.load({
454
576
  setReflectBusy(true); setActMsg('')
455
577
  apiPost(API.reflectAuto, {}).then(function (d) {
456
578
  setActMsg(d.result || t('generated')); setReflectBusy(false)
457
- apiGet(API.state).then(function (s) { if (s) setState(s) }).catch(function () {})
579
+ apiGet(API.state, { ws: currentWs() }).then(function (s) { if (s) setState(s) }).catch(function () {})
458
580
  }).catch(function (e) { setActMsg(t('failed') + e.message); setReflectBusy(false) })
459
581
  }
460
582
  return h('div', null,
461
583
  // 今日问候卡:问候语 + 昨天时间轴 + 提醒(纯 GUI 渲染,不干扰对话流)
462
- h(GreetingCard, { greeting: state.greeting, periodSummary: state.periodSummary, t: t }),
584
+ h(GreetingCard, { greeting: state.greeting, periodSummary: state.periodSummary, t: t, nonce: props && props.nonce }),
585
+ // 跨工作区总结(所有工作区的小总结 + 区内细分)
586
+ h('div', { style: { marginTop: '10px' } },
587
+ h('div', { style: { fontWeight: 700, fontSize: 'calc(12.5px * var(--dam-scale))', opacity: 0.75, marginBottom: '6px' } }, t('wsOverview')),
588
+ wsData && wsData.workspaces && wsData.workspaces.length
589
+ ? wsData.workspaces.map(function (ws) {
590
+ return h('div', { key: ws.path, style: { border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.22)) 60%, transparent)', borderRadius: '8px', padding: '7px 9px', marginBottom: '6px', background: 'color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.05)) 40%, transparent)' } },
591
+ h('div', { style: { fontWeight: 600, fontSize: 'calc(12px * var(--dam-scale))' } }, ws.name + (ws.dateRange ? ' · ' + ws.dateRange : '')),
592
+ ws.summary ? h('div', { style: { fontSize: 'calc(12px * var(--dam-scale))', lineHeight: 1.5, marginTop: '3px' } }, ws.summary) : h('div', { 'data-dam-hint': '' }, t('wsNoSummary')),
593
+ (ws.items && ws.items.length) ? ws.items.map(function (it, i) {
594
+ return h('div', { key: i, style: { fontSize: 'calc(11.5px * var(--dam-scale))', marginTop: '2px', opacity: 0.75 } }, '· ' + it)
595
+ }) : null)
596
+ })
597
+ : h('div', { 'data-dam-hint': '' }, t('wsGenerating'))),
463
598
  state.pendingReflection
464
599
  ? h(Banner, null, t('pendingReflection') + state.pendingReflection + t('pendingReflectionHint'))
465
600
  : null,
@@ -468,6 +603,12 @@ window.__ModuleLoader__.load({
468
603
  h('b', null, t('todayWork')), h('span', null, state.todayEntries + t('logEntries')),
469
604
  h('b', null, t('dailyReflection')), h('span', null, state.latestReflectionDate || t('notYet')),
470
605
  h('b', null, t('workspace')), h('span', null, state.ws)),
606
+ // 自动沉淀即时反馈(本轮/今日沉淀了多少条、最近一次时间)
607
+ state.autoStats && state.autoStats.count > 0
608
+ ? h('div', { 'data-dam-hint': '', style: { marginTop: '2px' } },
609
+ t('autoSettledToday') + state.autoStats.count + t('autoSettledSuffix') +
610
+ (state.autoStats.lastAt ? ' (' + t('autoSettledRecent') + new Date(state.autoStats.lastAt).toLocaleTimeString() + ')' : ''))
611
+ : h('div', { 'data-dam-hint': '', style: { marginTop: '2px' } }, t('autoSettledNone')),
471
612
  // 快捷操作
472
613
  h('div', { 'data-dam-row': '' },
473
614
  h('button', { 'data-dam-btn': '', onClick: oneClickReflect, disabled: reflectBusy }, reflectBusy ? t('reflecting') : t('oneClickReflect')),
@@ -627,22 +768,69 @@ window.__ModuleLoader__.load({
627
768
  var resultPair = useState(null)
628
769
  var result = resultPair[0]
629
770
  var setResult = resultPair[1]
771
+ var smartPair = useState(null)
772
+ var smart = smartPair[0]
773
+ var setSmart = smartPair[1]
630
774
  var busyPair = useState(false)
631
775
  var busy = busyPair[0]
632
776
  var setBusy = busyPair[1]
633
777
  function search() {
634
778
  if (!q.trim() || busy) return
635
- setBusy(true); setResult(null)
779
+ setBusy(true); setResult(null); setSmart(null)
636
780
  apiPost(API.recall, { query: q.trim() }).then(function (d) { setResult(d.result); setBusy(false) })
637
781
  .catch(function (e) { setResult(t('searchFailed') + e.message); setBusy(false) })
638
782
  }
783
+ function smartSearch() {
784
+ if (!q.trim() || busy) return
785
+ setBusy(true); setResult(null); setSmart(null)
786
+ apiPost(API.smartRecall, { query: q.trim() }).then(function (d) { setSmart(d); setBusy(false) })
787
+ .catch(function (e) { setSmart({ answer: t('searchFailed') + e.message, keywords: [], hits: [] }); setBusy(false) })
788
+ }
639
789
  return h('div', null,
640
790
  h('div', { 'data-dam-row': '' },
641
791
  h('input', { 'data-dam-input': '', placeholder: t('searchPlaceholder'), value: q, onChange: function (e) { setQ(e.target.value) }, onKeyDown: function (e) { if (e.key === 'Enter') search() } }),
642
- h('button', { 'data-dam-btn': '', onClick: search, disabled: busy }, t('searchBtn'))),
792
+ h('button', { 'data-dam-btn': '', onClick: search, disabled: busy }, t('searchBtn')),
793
+ h('button', { 'data-dam-btn': '', onClick: smartSearch, disabled: busy }, t('smartSearch'))),
794
+ smart ? h(Card, { title: t('smartAnswer') },
795
+ h('div', { 'data-dam-content': '' }, smart.answer || t('empty')),
796
+ smart.keywords && smart.keywords.length ? h('div', { 'data-dam-hint': '', style: { marginTop: '6px' } }, t('keywordsLabel') + smart.keywords.join(' / ')) : null,
797
+ smart.hits && smart.hits.length ? h('div', { 'data-dam-hint': '', style: { marginTop: '6px' } },
798
+ smart.hits.map(function (h, i) { return h('div', { key: i, style: { marginTop: '3px' } }, '· [' + h.where + '] ' + h.line) })) : null)
799
+ : null,
643
800
  result ? h(Card, { title: t('resultTitle') }, h('div', { 'data-dam-content': '' }, result)) : null)
644
801
  }
645
802
 
803
+ // ───────────────────────── 工作区总览页签(跨工作区全局总结) ─────────────────────────
804
+ function WorkspaceTab() {
805
+ var dataPair = useState(null)
806
+ var data = dataPair[0]
807
+ var setData = dataPair[1]
808
+ var busyPair = useState(false)
809
+ var busy = busyPair[0]
810
+ var setBusy = busyPair[1]
811
+ function load(force) {
812
+ if (busy) return
813
+ setBusy(true)
814
+ apiPost(API.workspaces, { force: !!force }).then(function (d) { if (d) setData(d); setBusy(false) })
815
+ .catch(function () { setBusy(false) })
816
+ }
817
+ useEffect(function () { load(false) }, [])
818
+ if (!data) return h('div', null, busy ? h('div', { 'data-dam-hint': '' }, t('wsGenerating')) : h(Loading))
819
+ return h('div', null,
820
+ h('div', { 'data-dam-row': '' },
821
+ h('button', { 'data-dam-btn': '', onClick: function () { load(true) }, disabled: busy }, busy ? t('refreshing') : t('refresh')),
822
+ h('span', { 'data-dam-hint': '' }, data.cached ? (t('generatedAt') + new Date(data.generatedAt).toLocaleTimeString()) : '')),
823
+ (data.workspaces && data.workspaces.length) ? data.workspaces.map(function (ws) {
824
+ return h(Card, { key: ws.path, title: ws.name + (ws.dateRange ? ' · ' + ws.dateRange : '') },
825
+ ws.summary ? h('div', { 'data-dam-content': '' }, ws.summary) : h('div', { 'data-dam-hint': '' }, t('wsNoSummary')),
826
+ (ws.items && ws.items.length) ? h('div', { style: { marginTop: '6px' } },
827
+ ws.items.map(function (it, i) {
828
+ return h('div', { key: i, style: { fontSize: 'calc(12px * var(--dam-scale))', marginTop: '3px', lineHeight: 1.5 } }, '· ' + it)
829
+ })) : null,
830
+ h('div', { 'data-dam-hint': '', style: { marginTop: '4px' } }, ws.path + ' · ' + (ws.logCount || 0) + t('logEntries')))
831
+ }) : h('div', { 'data-dam-hint': '' }, t('wsNone')))
832
+ }
833
+
646
834
  // ───────────────────────── 日历页签 ─────────────────────────
647
835
  var QUADRANT_STYLE = {
648
836
  '重要紧急': { color: 'var(--dsw-alias-state-error-primary, #d64545)', label: t('qUrgentImportant') },
@@ -702,12 +890,12 @@ window.__ModuleLoader__.load({
702
890
  // 图例
703
891
  rows.push(h('div', { 'data-dam-row': '', style: { flexWrap: 'wrap' } },
704
892
  Object.keys(QUADRANT_STYLE).map(function (q) {
705
- return h('span', { key: q, style: { fontSize: '11px', opacity: 0.8, marginRight: '10px' } },
893
+ return h('span', { key: q, style: { fontSize: 'calc(11px * var(--dam-scale))', opacity: 0.8, marginRight: '10px' } },
706
894
  h('span', { style: { display: 'inline-block', width: 8, height: 8, borderRadius: 2, background: QUADRANT_STYLE[q].color, marginRight: 4 } }), QUADRANT_STYLE[q].label)
707
895
  })))
708
896
  // 星期表头
709
897
  rows.push(h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: '3px', marginBottom: '4px' } },
710
- dowLabels.map(function (dl) { return h('div', { key: dl, style: { textAlign: 'center', fontSize: '11px', opacity: 0.55 } }, dl) })))
898
+ dowLabels.map(function (dl) { return h('div', { key: dl, style: { textAlign: 'center', fontSize: 'calc(11px * var(--dam-scale))', opacity: 0.55 } }, dl) })))
711
899
  // 日历格子
712
900
  var gridRows = []
713
901
  for (var ci = 0; ci < cells.length; ci += 7) {
@@ -728,7 +916,7 @@ window.__ModuleLoader__.load({
728
916
  overflow: 'hidden',
729
917
  },
730
918
  },
731
- h('div', { style: { fontSize: '11.5px', fontWeight: isToday ? 700 : 500, opacity: isToday ? 1 : 0.7, marginBottom: '3px' } }, dayNum),
919
+ h('div', { style: { fontSize: 'calc(11.5px * var(--dam-scale))', fontWeight: isToday ? 700 : 500, opacity: isToday ? 1 : 0.7, marginBottom: '3px' } }, dayNum),
732
920
  es.slice(0, 3).map(function (en) {
733
921
  var qs = QUADRANT_STYLE[en.quadrant] || QUADRANT_STYLE['未分类']
734
922
  return h('div', {
@@ -736,14 +924,14 @@ window.__ModuleLoader__.load({
736
924
  title: en.time + ' ' + en.title,
737
925
  onClick: function (ev) { ev.stopPropagation(); toggleDone(en) },
738
926
  style: {
739
- fontSize: '10.5px', lineHeight: 1.35, padding: '1px 4px', borderRadius: 4, marginBottom: 2,
927
+ fontSize: 'calc(10.5px * var(--dam-scale))', lineHeight: 1.35, padding: '1px 4px', borderRadius: 4, marginBottom: 2,
740
928
  background: 'color-mix(in srgb, ' + qs.color + ' 18%, transparent)',
741
929
  color: qs.color, textDecoration: en.done ? 'line-through' : 'none',
742
930
  opacity: en.done ? 0.5 : 1, cursor: 'pointer', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
743
931
  },
744
932
  }, (en.time && en.time !== '--:--' ? en.time + ' ' : '') + en.title)
745
933
  }),
746
- es.length > 3 ? h('div', { style: { fontSize: '10px', opacity: 0.5 } }, '+' + (es.length - 3)) : null))
934
+ es.length > 3 ? h('div', { style: { fontSize: 'calc(10px * var(--dam-scale))', opacity: 0.5 } }, '+' + (es.length - 3)) : null))
747
935
  })(cells[ci + cj])
748
936
  }
749
937
  gridRows.push(h('div', { key: 'w' + ci, style: { display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: '3px' } }, week))
@@ -883,23 +1071,29 @@ window.__ModuleLoader__.load({
883
1071
  var tabPair = useState('overview')
884
1072
  var tab = tabPair[0]
885
1073
  var setTab = tabPair[1]
1074
+ // 刷新 nonce:每次打开面板 / 点 ⟳ 时递增,驱动各页签重拉数据
1075
+ var noncePair = useState(0)
1076
+ var nonce = noncePair[0]
1077
+ var setNonce = noncePair[1]
886
1078
  var g = controller.geom()
887
1079
  useEffect(function () { return controller.subscribe(tick[1]) }, [])
888
1080
  if (!panelOpen && !panelClosing) return null
889
1081
  var body
890
- if (tab === 'overview') body = h(OverviewTab)
1082
+ if (tab === 'overview') body = h(OverviewTab, { nonce: nonce })
891
1083
  else if (tab === 'logs') body = h(LogsTab)
892
1084
  else if (tab === 'notes') body = h(NotesTab)
893
1085
  else if (tab === 'reflections') body = h(ReflectionsTab)
894
1086
  else if (tab === 'connect') body = h(ConnectTab)
895
1087
  else if (tab === 'calendar') body = h(CalendarTab)
1088
+ else if (tab === 'workspaces') body = h(WorkspaceTab)
896
1089
  else body = h(SearchTab)
897
- var tabs = [['overview', t('overview')], ['logs', t('logs')], ['notes', t('notes')], ['reflections', t('reflections')], ['connect', t('connect')], ['calendar', t('calendar')], ['search', t('search')]]
1090
+ var tabs = [['overview', t('overview')], ['logs', t('logs')], ['notes', t('notes')], ['reflections', t('reflections')], ['connect', t('connect')], ['calendar', t('calendar')], ['search', t('search')], ['workspaces', t('workspaces')]]
898
1091
  var style = {
899
1092
  left: g.left + 'px',
900
1093
  top: g.top + 'px',
901
1094
  width: g.width + 'px',
902
1095
  height: g.height + 'px',
1096
+ '--dam-scale': FONT_SCALE_VALUES[fontScale] || '1',
903
1097
  }
904
1098
  var dragMove = startPointerDrag(function (dx, dy) {
905
1099
  controller.setGeom({ left: g.left + dx, top: g.top + dy })
@@ -909,6 +1103,7 @@ window.__ModuleLoader__.load({
909
1103
  })
910
1104
  return h('div', {
911
1105
  'data-dam-panel': '',
1106
+ 'data-scale': fontScale in FONT_SCALES ? fontScale : 'md',
912
1107
  style: style,
913
1108
  'data-closing': panelClosing ? 'true' : undefined,
914
1109
  'data-dragging': dragActive ? 'true' : undefined,
@@ -920,7 +1115,7 @@ window.__ModuleLoader__.load({
920
1115
  h('strong', null, t('autoMemory')),
921
1116
  h('span', { className: 'dam-spacer' }),
922
1117
  h('button', { 'data-dam-btn': '', title: t('resetPos'), onClick: function () { controller.resetGeom() } }, '⤾'),
923
- h('button', { 'data-dam-btn': '', title: t('refresh'), onClick: function () { tick[1]() } }, '⟳'),
1118
+ h('button', { 'data-dam-btn': '', title: t('refresh'), onClick: function () { setNonce(nonce + 1) } }, '⟳'),
924
1119
  h('button', { 'data-dam-btn': '', title: t('close'), onClick: function () { controller.close() } }, '✕')),
925
1120
  h('div', { 'data-dam-tabs': '' }, tabs.map(function (t) {
926
1121
  return h('button', { key: t[0], 'data-dam-tab': '', 'data-active': tab === t[0] ? 'true' : undefined, onClick: function () { setTab(t[0]) } }, t[1])
@@ -929,10 +1124,70 @@ window.__ModuleLoader__.load({
929
1124
  h('div', { 'data-dam-resize': '', title: t('dragResize'), onPointerDown: resizeMove }))
930
1125
  }
931
1126
 
1127
+ // ───────────────────────── 调试中心(为提 issue 提供诊断) ─────────────────────────
1128
+ function DebugCenter() {
1129
+ var dataPair = useState(null)
1130
+ var data = dataPair[0]
1131
+ var setData = dataPair[1]
1132
+ var probesPair = useState(null)
1133
+ var probes = probesPair[0]
1134
+ var setProbes = probesPair[1]
1135
+ var busyPair = useState(false)
1136
+ var busy = busyPair[0]
1137
+ var setBusy = busyPair[1]
1138
+ function refresh() {
1139
+ if (busy) return
1140
+ setBusy(true)
1141
+ var probesDone = {}
1142
+ var probeList = [
1143
+ ['state', API.state, 'GET'], ['config', API.config, 'GET'], ['list', API.list, 'GET'],
1144
+ ['file', API.file, 'GET'], ['calendar', API.calendar, 'GET'], ['debug', API.debug, 'GET'],
1145
+ ['recall', API.recall, 'POST'], ['summarize', API.summarize, 'POST'], ['greet', API.greet, 'POST'],
1146
+ ['workspaces', API.workspaces, 'POST'], ['reflectAuto', API.reflectAuto, 'POST'],
1147
+ ]
1148
+ Promise.all(probeList.map(function (pr) {
1149
+ var t0 = Date.now()
1150
+ return fetch(pr[1], pr[2] === 'POST' ? { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' } : undefined)
1151
+ .then(function (r) { probesDone[pr[0]] = { status: r.status, ms: Date.now() - t0 } })
1152
+ .catch(function (e) { probesDone[pr[0]] = { error: e.message } })
1153
+ })).then(function () {
1154
+ setProbes(probesDone)
1155
+ return apiGet(API.debug)
1156
+ }).then(function (d) { setData(d); setBusy(false) }).catch(function () { setBusy(false) })
1157
+ }
1158
+ useEffect(function () { refresh() }, [])
1159
+ if (!data) return h('div', { 'data-dam-hint': '' }, t('dbgLoading'))
1160
+ function kv(label, value, warn) {
1161
+ return h('div', { 'data-dam-row': '', style: { marginBottom: '3px' } },
1162
+ h('span', { style: { flex: '0 0 140px', opacity: .7, fontSize: 'calc(11.5px * var(--dam-scale))' } }, label),
1163
+ h('span', { style: { fontSize: 'calc(11.5px * var(--dam-scale))', wordBreak: 'break-all', color: warn ? 'var(--dsw-alias-state-warn-primary, #e6a23c)' : undefined } }, String(value)))
1164
+ }
1165
+ var hb = data.heartbeat || {}
1166
+ var hbAge = hb.exists && hb.heartbeatAt ? Math.round((Date.now() - hb.heartbeatAt) / 1000) : -1
1167
+ var apiBad = probes && Object.keys(probes).some(function (k) { return probes[k].status === 404 || probes[k].error })
1168
+ return h('div', null,
1169
+ h('div', { 'data-dam-row': '' },
1170
+ h('button', { 'data-dam-btn': '', onClick: refresh, disabled: busy }, busy ? t('dbgRefreshing') : t('dbgRefresh')),
1171
+ h('span', { 'data-dam-hint': '' }, data.host.indexPath || '')),
1172
+ kv('插件版本', data.host.version || '(读不到)', false),
1173
+ kv('PID / 启动', data.host.pid + ' / ' + (data.host.startTime ? new Date(data.host.startTime).toLocaleTimeString() : ''), false),
1174
+ kv('Host 需重启?', data.host.needsRestart ? '是(代码比进程新)' : '否', data.host.needsRestart),
1175
+ kv('轮询心跳', hb.exists ? (hbAge >= 0 ? '存活(上次 ' + hbAge + ' 秒前)' : '存活') : '无心跳文件', !hb.exists || hbAge > 360),
1176
+ kv('沉淀重试队列', data.autoConsolidate.pendingQueue + ' 条', data.autoConsolidate.pendingQueue > 0),
1177
+ kv('今日沉淀', data.autoConsolidate.stats.count + ' 条 / 最近 ' + (data.autoConsolidate.stats.lastAt ? new Date(data.autoConsolidate.stats.lastAt).toLocaleTimeString() : '无'), false),
1178
+ kv('正在沉淀中', data.autoConsolidate.consolidating ? '是' : '否', data.autoConsolidate.consolidating),
1179
+ kv('subagents', data.subagents.available ? (data.subagents.providers.join(', ') || '可用') : '不可用', !data.subagents.available),
1180
+ kv('项目笔记重复标题', data.duplicateHeadings + ' 个', data.duplicateHeadings > 0),
1181
+ kv('记忆文件', '用户 ' + (data.memoryFiles.user.exists ? fmtSize(data.memoryFiles.user.size) : '缺失') + ' · 笔记 ' + (data.memoryFiles.notes.exists ? fmtSize(data.memoryFiles.notes.size) : '缺失') + ' · 日志 ' + (data.memoryFiles.log.exists ? fmtSize(data.memoryFiles.log.size) : '缺失'), !data.memoryFiles.log.exists),
1182
+ kv('当前工作区', currentWs() || '(未取到)', false),
1183
+ kv('API 连通', probes ? Object.keys(probes).map(function (k) { return k + '=' + (probes[k].status !== undefined ? probes[k].status : probes[k].error) }).join(' ') : '…', apiBad))
1184
+ }
1185
+
932
1186
  // ───────────────────────── 设置页 ─────────────────────────
933
1187
  var STYLE_OPTIONS = [['auto', t('styleAuto')], ['life', t('styleLife')], ['professional', t('styleProfessional')]]
934
1188
  var LOCALE_OPTIONS = [['zh', t('zh')], ['en', t('en')]]
935
1189
  function SettingsPage() {
1190
+ var tickPair = useTick()
936
1191
  var cfgPair = useState(null)
937
1192
  var cfg = cfgPair[0]
938
1193
  var setCfg = cfgPair[1]
@@ -945,6 +1200,47 @@ window.__ModuleLoader__.load({
945
1200
  var errPair = useState('')
946
1201
  var err = errPair[0]
947
1202
  var setErr = errPair[1]
1203
+ var dbgOpenPair = useState(false)
1204
+ var dbgOpen = dbgOpenPair[0]
1205
+ var setDbgOpen = dbgOpenPair[1]
1206
+ var browseOpenPair = useState(false)
1207
+ var browseOpen = browseOpenPair[0]
1208
+ var setBrowseOpen = browseOpenPair[1]
1209
+ var browsePathPair = useState('')
1210
+ var browsePath = browsePathPair[0]
1211
+ var setBrowsePath = browsePathPair[1]
1212
+ var browseParentPair = useState('')
1213
+ var browseParent = browseParentPair[0]
1214
+ var setBrowseParent = browseParentPair[1]
1215
+ var browseDirsPair = useState(null)
1216
+ var browseDirs = browseDirsPair[0]
1217
+ var setBrowseDirs = browseDirsPair[1]
1218
+ function browseTo(p) {
1219
+ setBrowsePath(p)
1220
+ apiPost(API.browseDir, { path: p }).then(function (d) {
1221
+ if (d) { setBrowsePath(d.path); setBrowseParent(d.parent); setBrowseDirs(d.dirs || []) }
1222
+ }).catch(function () { setBrowseDirs([]) })
1223
+ }
1224
+ function openBrowser() {
1225
+ // 优先弹系统原生文件夹选择器(native 后端);不可用(远程/无显示)回退内嵌浏览
1226
+ apiPost(API.pickDir, {}).then(function (d) {
1227
+ if (d && d.native && d.dir) {
1228
+ set('memoryRoot', d.dir)
1229
+ setMsg(t('pickedDir') + ' ' + d.dir + ' ' + t('rememberSave'))
1230
+ } else if (d && d.native) {
1231
+ // 用户在系统对话框点了取消:保持原值,不动作
1232
+ } else {
1233
+ setMsg(t('pickerUnavailable'))
1234
+ setBrowseOpen(true)
1235
+ browseTo(cfg.memoryRoot || '')
1236
+ }
1237
+ }).catch(function () {
1238
+ setMsg(t('pickerUnavailable'))
1239
+ setBrowseOpen(true)
1240
+ browseTo(cfg.memoryRoot || '')
1241
+ })
1242
+ }
1243
+ useEffect(function () { return controller.subscribe(tickPair[1]) }, [])
948
1244
  useEffect(function () {
949
1245
  var alive = true
950
1246
  apiGet(API.config).then(function (d) { if (alive) setCfg(d.config) }).catch(function (e) { setErr(e.message) })
@@ -955,7 +1251,7 @@ window.__ModuleLoader__.load({
955
1251
  function save() {
956
1252
  if (busy) return
957
1253
  setBusy(true); setMsg(''); setErr('')
958
- apiPost(API.config, cfg).then(function (d) { setCfg(d.config); setMsg(t('saved')); setBusy(false) })
1254
+ apiPost(API.config, cfg).then(function (d) { setCfg(d.config); setMsg(t('saved') + (d.migrated ? ' ' + d.migrated : '')); setBusy(false) })
959
1255
  .catch(function (e) { setErr(e.message); setBusy(false) })
960
1256
  }
961
1257
  function field(label, control, hint) {
@@ -967,17 +1263,39 @@ window.__ModuleLoader__.load({
967
1263
  h('div', { 'data-dam-hint': '' }, t('settingsHeader')),
968
1264
  field(t('fLocale'), h('select', { 'data-dam-select': '', value: cfg.locale || 'zh', onChange: function (e) { set('locale', e.target.value); setLocale(e.target.value) } },
969
1265
  LOCALE_OPTIONS.map(function (o) { return h('option', { key: o[0], value: o[0] }, o[1]) })), t('fLocaleHint')),
1266
+ field(t('fFontSize'), h('select', { 'data-dam-select': '', value: fontScale, onChange: function (e) { fontScale = e.target.value; try { localStorage.setItem('dsh-auto-memory.fontScale.v2', fontScale) } catch (ee) {}; try { var pp = document.querySelector('[data-dam-panel]'); if (pp) pp.style.setProperty('--dam-scale', FONT_SCALE_VALUES[fontScale] || '1') } catch (ee2) {}; emit() } },
1267
+ Object.keys(FONT_SCALES).map(function (k) { return h('option', { key: k, value: k }, t('fs' + k.charAt(0).toUpperCase() + k.slice(1))) })), t('fFontSizeHint')),
970
1268
  field(t('fUserDir'), h('input', { 'data-dam-input': '', value: cfg.userMemoryDir, onChange: function (e) { set('userMemoryDir', e.target.value) } }), t('fUserDirHint')),
971
1269
  field(t('fProjectDir'), h('input', { 'data-dam-input': '', value: cfg.projectMemoryDir, onChange: function (e) { set('projectMemoryDir', e.target.value) } }), t('fProjectDirHint')),
1270
+ field(t('fMemoryRoot'), h('div', { 'data-dam-row': '', style: { flex: 1 } },
1271
+ h('input', { 'data-dam-input': '', style: { flex: 1 }, value: cfg.memoryRoot || '', onChange: function (e) { set('memoryRoot', e.target.value) } }),
1272
+ h('button', { 'data-dam-btn': '', onClick: function () { openBrowser() } }, t('fBrowse'))), t('fMemoryRootHint')),
1273
+ browseOpen ? h('div', { style: { border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.25)) 60%, transparent)', borderRadius: '8px', padding: '8px', marginBottom: '8px', background: 'color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.06)) 40%, transparent)' } },
1274
+ h('div', { 'data-dam-row': '' },
1275
+ h('b', { style: { fontSize: 'calc(12px * var(--dam-scale))', wordBreak: 'break-all', flex: 1 } }, browsePath || '…'),
1276
+ h('button', { 'data-dam-btn': '', onClick: function () { browseTo(browseParent) }, disabled: browseParent === browsePath }, t('fUp'))),
1277
+ h('div', { style: { maxHeight: '160px', overflow: 'auto', marginTop: '4px' } },
1278
+ (browseDirs || []).map(function (d) {
1279
+ return h('button', { key: d.path, 'data-dam-btn': '', style: { display: 'block', width: '100%', textAlign: 'left', padding: '3px 6px' }, onClick: function () { browseTo(d.path) } }, '📁 ' + d.name)
1280
+ }).concat(browseDirs && !browseDirs.length ? [h('div', { key: 'e', 'data-dam-hint': '' }, t('empty'))] : [])),
1281
+ h('div', { 'data-dam-row': '', style: { marginTop: '6px' } },
1282
+ h('button', { 'data-dam-btn': '', onClick: function () { set('memoryRoot', browsePath); setBrowseOpen(false) } }, t('fSelectDir')),
1283
+ h('button', { 'data-dam-btn': '', onClick: function () { setBrowseOpen(false) } }, t('close'))))
1284
+ : null,
972
1285
  field(t('fInject'), h('input', { type: 'checkbox', checked: !!cfg.injectEnabled, onChange: function (e) { set('injectEnabled', e.target.checked) } }), t('fInjectHint')),
973
1286
  field(t('fBudget'), h('input', { 'data-dam-input': '', type: 'number', min: 400, value: cfg.injectBudgetChars, onChange: function (e) { set('injectBudgetChars', Number(e.target.value) || 2400) } }), t('fBudgetHint')),
974
1287
  field(t('fDays'), h('input', { 'data-dam-input': '', type: 'number', min: 1, max: 14, value: cfg.recentDaysInjected, onChange: function (e) { set('recentDaysInjected', Number(e.target.value) || 3) } }), t('fDaysHint')),
1288
+ field(t('fDayBoundary'), h('input', { 'data-dam-input': '', type: 'number', min: 0, max: 1439, value: (cfg.dayBoundaryMinutes === undefined ? 450 : cfg.dayBoundaryMinutes), onChange: function (e) { set('dayBoundaryMinutes', parseInt(e.target.value || '0', 10)) } }), t('fDayBoundaryHint')),
975
1289
  field(t('fReflect'), h('input', { type: 'checkbox', checked: !!cfg.reflectEnabled, onChange: function (e) { set('reflectEnabled', e.target.checked) } }), t('fReflectHint')),
976
1290
  field(t('fStyle'), h('select', { 'data-dam-select': '', value: cfg.reflectStyle, onChange: function (e) { set('reflectStyle', e.target.value) } },
977
1291
  STYLE_OPTIONS.map(function (o) { return h('option', { key: o[0], value: o[0] }, o[1]) })), t('fStyleHint')),
978
1292
  h('div', { 'data-dam-row': '' },
979
1293
  h('button', { 'data-dam-btn': '', onClick: save, disabled: busy }, busy ? t('saving') : t('saveSettings')),
980
1294
  msg ? h('span', null, msg) : null),
1295
+ // 调试中心(折叠):模块状态一览,方便排查问题/提 issue
1296
+ h('div', { style: { marginTop: '14px', borderTop: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.2)) 55%, transparent)', paddingTop: '10px' } },
1297
+ h('button', { 'data-dam-btn': '', onClick: function () { setDbgOpen(!dbgOpen) } }, (dbgOpen ? '▴ ' : '▾ ') + t('debugCenter')),
1298
+ dbgOpen ? h('div', { style: { marginTop: '8px' } }, h(DebugCenter)) : null),
981
1299
  err ? h('div', { 'data-dam-error': '' }, err) : null)
982
1300
  }
983
1301
 
@@ -994,7 +1312,13 @@ window.__ModuleLoader__.load({
994
1312
 
995
1313
  var slots = ctx.slots
996
1314
  if (!slots) { console.warn('[dsh-auto-memory] slots service unavailable'); return }
1315
+ sessions = ctx.sessions
997
1316
 
1317
+ // 初始化界面字号(本地偏好,localStorage 持久化)
1318
+ try {
1319
+ var savedScale = localStorage.getItem('dsh-auto-memory.fontScale.v2')
1320
+ if (savedScale in FONT_SCALES) fontScale = savedScale
1321
+ } catch (e3) {}
998
1322
  // 初始化界面语言(从 host 配置读取,持久化到 localStorage 以便即时生效)
999
1323
  try {
1000
1324
  var savedLocale = localStorage.getItem('dsh-auto-memory.locale')
@@ -1025,7 +1349,7 @@ window.__ModuleLoader__.load({
1025
1349
  console.log('[dsh-auto-memory] client ready: sidebar entry + panel + settings page')
1026
1350
  }
1027
1351
 
1028
- exports.inject = ['slots']
1352
+ exports.inject = ['slots', 'sessions']
1029
1353
  exports.apply = apply
1030
1354
  return module.exports
1031
1355
  },