@modusensus/dsh-mneme 0.7.13 → 0.7.15
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/README.en.md +2 -2
- package/README.md +7 -5
- package/lib/api.js +338 -7
- package/lib/client.js +1238 -204
- package/lib/index.js +25 -2
- package/lib/mirror.js +93 -59
- package/lib/service.js +2 -0
- package/lib/settings.js +174 -0
- package/lib/store.js +77 -4
- package/lib/summarize.js +8 -8
- package/package.json +12 -1
- package/scripts/check-sync.js +42 -0
- package/src/api.js +338 -7
- package/src/index.js +25 -2
- package/src/mirror.js +93 -59
- package/src/service.js +2 -0
- package/src/settings.js +174 -0
- package/src/store.js +77 -4
- package/src/summarize.js +8 -8
- package/test/api.test.js +485 -2
- package/test/client.test.js +82 -52
- package/test/helpers/peer-worker.mjs +17 -2
- package/test/peer-blockers.test.js +9 -1
- package/test/settings.test.js +136 -0
- package/test/summarize.test.js +28 -0
package/lib/client.js
CHANGED
|
@@ -158,7 +158,7 @@ window.__ModuleLoader__.load({
|
|
|
158
158
|
"memory.explorer.importance": "重要性",
|
|
159
159
|
"memory.explorer.topK": "返回数量",
|
|
160
160
|
"memory.explorer.topKOption": "返回 {n} 条",
|
|
161
|
-
"memory.card.open": "
|
|
161
|
+
"memory.card.open": "在记忆库中查看全文",
|
|
162
162
|
"memory.time.now": "刚刚",
|
|
163
163
|
"memory.time.seconds": "{n}秒前",
|
|
164
164
|
"memory.time.minutes": "{n}分钟前",
|
|
@@ -212,6 +212,136 @@ window.__ModuleLoader__.load({
|
|
|
212
212
|
"memory.settings.extapi.copied": "已复制",
|
|
213
213
|
"memory.settings.extapi.savedHint": "已保存,重启 DSH 后生效",
|
|
214
214
|
"memory.settings.extapi.invalidPort": "端口需为 1-65535 的数字",
|
|
215
|
+
"memory.tab.rejected_solution": "被否决方案",
|
|
216
|
+
"memory.tab.pitfall": "踩坑记录",
|
|
217
|
+
"memory.tab.constraint": "工程约束",
|
|
218
|
+
"memory.features.title": "功能开关",
|
|
219
|
+
"memory.features.desc": "按需启停后端能力,改动保存后重启 DSH 生效。",
|
|
220
|
+
"memory.features.group.core": "核心",
|
|
221
|
+
"memory.features.group.enhance": "记忆增强",
|
|
222
|
+
"memory.features.group.dream": "巩固与睡眠",
|
|
223
|
+
"memory.features.group.advanced": "高级",
|
|
224
|
+
"memory.features.advancedToggle": "高级(注入策略 · 反思 · 实验项)",
|
|
225
|
+
"memory.features.restartHint": "重启 DSH 后生效",
|
|
226
|
+
"memory.features.loadFailed": "加载失败",
|
|
227
|
+
"memory.features.autoInject": "自动注入",
|
|
228
|
+
"memory.features.autoInject.hint": "每轮对话自动携带相关记忆",
|
|
229
|
+
"memory.features.autoSummarize": "自动总结",
|
|
230
|
+
"memory.features.autoSummarize.hint": "对话结束自动提炼记忆条目",
|
|
231
|
+
"memory.features.hotMemoryEnabled": "热记忆",
|
|
232
|
+
"memory.features.hotMemoryEnabled.hint": "最近几轮对话原文随注入携带,不写入长期记忆",
|
|
233
|
+
"memory.features.entityExtractionEnabled": "实体抽取",
|
|
234
|
+
"memory.features.entityExtractionEnabled.hint": "从记忆中提取人物/项目/概念,图谱随之生长",
|
|
235
|
+
"memory.features.codingRetrospect": "编码记忆蒸馏",
|
|
236
|
+
"memory.features.codingRetrospect.hint": "用完整转录(含工具调用与报错)提炼踩坑、约束与被否决方案",
|
|
237
|
+
"memory.features.rerankEnabled": "结果重排",
|
|
238
|
+
"memory.features.rerankEnabled.hint": "本地重排模型对召回结果精排,更慢更准",
|
|
239
|
+
"memory.features.searchSemanticDedup": "语义去重",
|
|
240
|
+
"memory.features.searchSemanticDedup.hint": "搜索结果中意思相近的条目只保留一条",
|
|
241
|
+
"memory.features.autoDream": "记忆巩固",
|
|
242
|
+
"memory.features.autoDream.hint": "后台自动合并、沉淀碎片记忆",
|
|
243
|
+
"memory.features.sleepModeEnabled": "睡眠模式",
|
|
244
|
+
"memory.features.sleepModeEnabled.hint": "空闲时段做分层压缩与整理",
|
|
245
|
+
"memory.features.hybridInject": "混合注入",
|
|
246
|
+
"memory.features.hybridInject.hint": "关键词 + 向量双路召回后合并注入",
|
|
247
|
+
"memory.features.selectiveInjectEnabled": "选择性注入",
|
|
248
|
+
"memory.features.selectiveInjectEnabled.hint": "按相关性筛选,只注入值得携带的记忆",
|
|
249
|
+
"memory.features.adaptiveThresholdEnabled": "自适应阈值",
|
|
250
|
+
"memory.features.adaptiveThresholdEnabled.hint": "按召回质量动态调整相关性门槛",
|
|
251
|
+
"memory.features.reflectionUpdateEnabled": "反思更新",
|
|
252
|
+
"memory.features.reflectionUpdateEnabled.hint": "巩固时允许修正已有记忆",
|
|
253
|
+
"memory.features.distill": "蒸馏与限流",
|
|
254
|
+
"memory.features.distillRateLimitIntervalMs": "蒸馏放行间隔 (ms)",
|
|
255
|
+
"memory.features.distillRateLimitRetries": "429 重试次数",
|
|
256
|
+
"memory.features.distillRateLimitBaseDelayMs": "退避起始延迟 (ms)",
|
|
257
|
+
"memory.features.distillMaxChars": "转录截断上限(字符)",
|
|
258
|
+
"memory.features.codingBoostFactor": "编码记忆加权",
|
|
259
|
+
"memory.features.memoryQualityFilter.enabled": "记忆质量过滤",
|
|
260
|
+
"memory.features.memoryQualityFilter.enabled.hint": "低价值记忆自动归档,注入按质量降权",
|
|
261
|
+
"memory.features.llmAudit.enabled": "后台调用审计",
|
|
262
|
+
"memory.features.llmAudit.enabled.hint": "记录巩固/总结的后台模型调用与 token 消耗",
|
|
263
|
+
"memory.features.bm25SearchEnabled": "BM25 关键词检索",
|
|
264
|
+
"memory.features.bm25SearchEnabled.hint": "传统关键词打分检索,与向量召回互补",
|
|
265
|
+
"memory.features.conflictFreezeEnabled": "冲突冻结",
|
|
266
|
+
"memory.features.conflictFreezeEnabled.hint": "巩固发现互相矛盾的记忆时先冻结待确认",
|
|
267
|
+
"memory.features.trustEpistemicWeighting": "可信度加权",
|
|
268
|
+
"memory.features.trustEpistemicWeighting.hint": "按来源可信度调整召回排序(实验性)",
|
|
269
|
+
"memory.features.reflectionFailureTracking": "反思失败追踪",
|
|
270
|
+
"memory.features.reflectionFailureTracking.hint": "记录巩固决策的失败样本供后续改进",
|
|
271
|
+
"memory.features.embedRoute": "语义检索路线",
|
|
272
|
+
"memory.features.embedProvider": "Embedding 提供方",
|
|
273
|
+
"memory.features.embedProvider.openai": "OpenAI 兼容接口",
|
|
274
|
+
"memory.features.embedProvider.local": "本地模型(离线)",
|
|
275
|
+
"memory.features.embedProvider.ollama": "Ollama",
|
|
276
|
+
"memory.features.embedProvider.openai.hint": "接口地址 / Key / 模型在「向量搜索」卡片配置",
|
|
277
|
+
"memory.features.embedProvider.local.hint": "首次使用会下载模型,之后完全离线",
|
|
278
|
+
"memory.features.embedProvider.ollama.hint": "需要本机 Ollama 服务在运行",
|
|
279
|
+
"memory.features.localEmbedModel": "本地 embedding 模型",
|
|
280
|
+
"memory.features.ollamaBaseUrl": "Ollama 服务地址",
|
|
281
|
+
"memory.features.ollamaModel": "Ollama 模型名",
|
|
282
|
+
"memory.features.dreamProvider": "巩固模型 Provider",
|
|
283
|
+
"memory.features.dreamModel": "巩固用模型名",
|
|
284
|
+
"memory.features.dreamModelHint": "留空 = 跟随主对话模型;只影响记忆巩固(autoDream)用的模型",
|
|
285
|
+
"memory.explorer.viewCards": "卡片",
|
|
286
|
+
"memory.explorer.viewTimeline": "时间线",
|
|
287
|
+
"memory.explorer.viewAria": "视图切换",
|
|
288
|
+
"memory.explorer.dateLabel": "时间",
|
|
289
|
+
"memory.explorer.date.all": "全部时间",
|
|
290
|
+
"memory.explorer.date.7d": "近 7 天",
|
|
291
|
+
"memory.explorer.date.30d": "近 30 天",
|
|
292
|
+
"memory.explorer.date.90d": "近 90 天",
|
|
293
|
+
"memory.explorer.more": "更多操作",
|
|
294
|
+
"memory.explorer.exportJson": "导出 JSON",
|
|
295
|
+
"memory.explorer.exportMarkdown": "导出 Markdown",
|
|
296
|
+
"memory.explorer.importMd": "导入 Markdown 镜像…",
|
|
297
|
+
"memory.explorer.importTitle": "导入 Markdown 镜像",
|
|
298
|
+
"memory.explorer.importHint": "选择一个镜像类型的 Markdown 文件,解析其中的人工编辑并合并进记忆库。",
|
|
299
|
+
"memory.explorer.importType": "镜像类型",
|
|
300
|
+
"memory.explorer.importPick": "选择文件…",
|
|
301
|
+
"memory.explorer.importConfirm": "导入",
|
|
302
|
+
"memory.explorer.importCancel": "取消",
|
|
303
|
+
"memory.explorer.importing": "导入中…",
|
|
304
|
+
"memory.explorer.imported": "已合并 {n} 条编辑",
|
|
305
|
+
"memory.explorer.importFailed": "导入失败",
|
|
306
|
+
"memory.explorer.importNoFile": "请先选择文件",
|
|
307
|
+
"memory.explorer.exportFailed": "导出失败",
|
|
308
|
+
"memory.explorer.conflictBadge": "冲突",
|
|
309
|
+
"memory.explorer.archivedBadge": "已归档",
|
|
310
|
+
"memory.explorer.detail.edit": "编辑",
|
|
311
|
+
"memory.explorer.detail.save": "保存",
|
|
312
|
+
"memory.explorer.detail.cancel": "取消",
|
|
313
|
+
"memory.explorer.detail.archive": "归档",
|
|
314
|
+
"memory.explorer.detail.archived": "已归档",
|
|
315
|
+
"memory.explorer.detail.archiveFailed": "操作失败",
|
|
316
|
+
"memory.explorer.detail.saved": "已保存,镜像同步更新",
|
|
317
|
+
"memory.explorer.detail.quality": "质量分",
|
|
318
|
+
"memory.explorer.detail.entities": "关联实体",
|
|
319
|
+
"memory.explorer.detail.entitiesEmpty": "开启实体抽取后,相关人物/项目会出现在这里",
|
|
320
|
+
"memory.explorer.detail.closeAria": "关闭详情",
|
|
321
|
+
"memory.explorer.detail.editTitle": "标题",
|
|
322
|
+
"memory.explorer.detail.editContent": "内容",
|
|
323
|
+
"memory.explorer.detail.editImportance": "重要性",
|
|
324
|
+
"memory.status.dream": "最近巩固",
|
|
325
|
+
"memory.status.dreamNever": "尚未运行",
|
|
326
|
+
"memory.status.conflicts": "待确认冲突",
|
|
327
|
+
"memory.status.conflictsHint": "冻结的矛盾记忆,等待人工确认",
|
|
328
|
+
"memory.status.workbench": "工作动态",
|
|
329
|
+
"memory.status.dreamConsolidate": "记忆巩固",
|
|
330
|
+
"memory.status.summarize": "总结提炼",
|
|
331
|
+
"memory.status.tokens": "{n} tokens",
|
|
332
|
+
"memory.status.deposited": "沉淀 {n} 条记忆",
|
|
333
|
+
"memory.status.failed": "失败",
|
|
334
|
+
"memory.status.success": "成功",
|
|
335
|
+
"memory.status.skipped": "跳过",
|
|
336
|
+
"memory.status.emptyFeed": "还没有后台活动:对话结束后会自动提炼记忆",
|
|
337
|
+
"memory.status.consolidated": "沉淀的记忆",
|
|
338
|
+
"memory.status.consolidatedEmpty": "autoDream / autoSummarize 沉淀的记忆会出现在这里",
|
|
339
|
+
"memory.status.archivedMemories": "已归档的记忆",
|
|
340
|
+
"memory.status.archivedEmpty": "没有归档的记忆",
|
|
341
|
+
"memory.status.restore": "恢复",
|
|
342
|
+
"memory.settings.extapi.reveal": "显示",
|
|
343
|
+
"memory.settings.extapi.hide": "隐藏",
|
|
344
|
+
"memory.settings.extapi.maskHint": "Token 已遮蔽;「复制」可复制完整值",
|
|
215
345
|
"memory.explorer.delete": "删除",
|
|
216
346
|
"memory.explorer.confirmDelete": "确认删除?",
|
|
217
347
|
"memory.explorer.cancel": "取消",
|
|
@@ -292,7 +422,7 @@ window.__ModuleLoader__.load({
|
|
|
292
422
|
"memory.explorer.importance": "Importance",
|
|
293
423
|
"memory.explorer.topK": "Results limit",
|
|
294
424
|
"memory.explorer.topKOption": "Return {n}",
|
|
295
|
-
"memory.card.open": "Open full text in the
|
|
425
|
+
"memory.card.open": "Open full text in the memory library",
|
|
296
426
|
"memory.time.now": "just now",
|
|
297
427
|
"memory.time.seconds": "{n}s ago",
|
|
298
428
|
"memory.time.minutes": "{n}m ago",
|
|
@@ -346,6 +476,136 @@ window.__ModuleLoader__.load({
|
|
|
346
476
|
"memory.settings.extapi.copied": "Copied",
|
|
347
477
|
"memory.settings.extapi.savedHint": "Saved. Takes effect after restarting DSH",
|
|
348
478
|
"memory.settings.extapi.invalidPort": "Port must be a number between 1 and 65535",
|
|
479
|
+
"memory.tab.rejected_solution": "Rejected solutions",
|
|
480
|
+
"memory.tab.pitfall": "Pitfalls",
|
|
481
|
+
"memory.tab.constraint": "Constraints",
|
|
482
|
+
"memory.features.title": "Features",
|
|
483
|
+
"memory.features.desc": "Toggle backend capabilities. Changes take effect after restarting DSH.",
|
|
484
|
+
"memory.features.group.core": "Core",
|
|
485
|
+
"memory.features.group.enhance": "Enhancement",
|
|
486
|
+
"memory.features.group.dream": "Consolidation",
|
|
487
|
+
"memory.features.group.advanced": "Advanced",
|
|
488
|
+
"memory.features.advancedToggle": "Advanced (injection · reflection · experimental)",
|
|
489
|
+
"memory.features.restartHint": "Takes effect after restarting DSH",
|
|
490
|
+
"memory.features.loadFailed": "Failed to load",
|
|
491
|
+
"memory.features.autoInject": "Auto injection",
|
|
492
|
+
"memory.features.autoInject.hint": "Carry relevant memories into every turn",
|
|
493
|
+
"memory.features.autoSummarize": "Auto summarization",
|
|
494
|
+
"memory.features.autoSummarize.hint": "Distill memory entries when a conversation ends",
|
|
495
|
+
"memory.features.hotMemoryEnabled": "Hot memory",
|
|
496
|
+
"memory.features.hotMemoryEnabled.hint": "Carry the last few turns verbatim; never written to long-term memory",
|
|
497
|
+
"memory.features.entityExtractionEnabled": "Entity extraction",
|
|
498
|
+
"memory.features.entityExtractionEnabled.hint": "Extract people / projects / concepts so the graph grows by itself",
|
|
499
|
+
"memory.features.codingRetrospect": "Coding retrospection",
|
|
500
|
+
"memory.features.codingRetrospect.hint": "Distill pitfalls, constraints and rejected solutions from full transcripts (tools and errors included)",
|
|
501
|
+
"memory.features.rerankEnabled": "Reranking",
|
|
502
|
+
"memory.features.rerankEnabled.hint": "Rerank recalled results with a local model — slower, more precise",
|
|
503
|
+
"memory.features.searchSemanticDedup": "Semantic dedup",
|
|
504
|
+
"memory.features.searchSemanticDedup.hint": "Keep one entry when search hits near-duplicates",
|
|
505
|
+
"memory.features.autoDream": "Memory consolidation",
|
|
506
|
+
"memory.features.autoDream.hint": "Merge and settle fragmented memories in the background",
|
|
507
|
+
"memory.features.sleepModeEnabled": "Sleep mode",
|
|
508
|
+
"memory.features.sleepModeEnabled.hint": "Layered compaction during idle periods",
|
|
509
|
+
"memory.features.hybridInject": "Hybrid injection",
|
|
510
|
+
"memory.features.hybridInject.hint": "Merge keyword + vector recall before injecting",
|
|
511
|
+
"memory.features.selectiveInjectEnabled": "Selective injection",
|
|
512
|
+
"memory.features.selectiveInjectEnabled.hint": "Only inject memories worth carrying, filtered by relevance",
|
|
513
|
+
"memory.features.adaptiveThresholdEnabled": "Adaptive threshold",
|
|
514
|
+
"memory.features.adaptiveThresholdEnabled.hint": "Adjust the relevance floor with recall quality",
|
|
515
|
+
"memory.features.reflectionUpdateEnabled": "Reflective update",
|
|
516
|
+
"memory.features.reflectionUpdateEnabled.hint": "Allow consolidation to revise existing memories",
|
|
517
|
+
"memory.features.distill": "Distillation & rate limiting",
|
|
518
|
+
"memory.features.distillRateLimitIntervalMs": "Distill spacing (ms)",
|
|
519
|
+
"memory.features.distillRateLimitRetries": "429 retries",
|
|
520
|
+
"memory.features.distillRateLimitBaseDelayMs": "Backoff base delay (ms)",
|
|
521
|
+
"memory.features.distillMaxChars": "Transcript cap (chars)",
|
|
522
|
+
"memory.features.codingBoostFactor": "Coding boost factor",
|
|
523
|
+
"memory.features.memoryQualityFilter.enabled": "Memory quality filter",
|
|
524
|
+
"memory.features.memoryQualityFilter.enabled.hint": "Archive low-value memories automatically; injection downranks by quality",
|
|
525
|
+
"memory.features.llmAudit.enabled": "Background call audit",
|
|
526
|
+
"memory.features.llmAudit.enabled.hint": "Log background model calls and token usage from consolidation / summaries",
|
|
527
|
+
"memory.features.bm25SearchEnabled": "BM25 keyword search",
|
|
528
|
+
"memory.features.bm25SearchEnabled.hint": "Classic keyword scoring, complementary to vector recall",
|
|
529
|
+
"memory.features.conflictFreezeEnabled": "Conflict freezing",
|
|
530
|
+
"memory.features.conflictFreezeEnabled.hint": "Freeze contradictory memories for confirmation during consolidation",
|
|
531
|
+
"memory.features.trustEpistemicWeighting": "Credibility weighting",
|
|
532
|
+
"memory.features.trustEpistemicWeighting.hint": "Adjust recall ranking by source credibility (experimental)",
|
|
533
|
+
"memory.features.reflectionFailureTracking": "Reflection failure tracking",
|
|
534
|
+
"memory.features.reflectionFailureTracking.hint": "Record failed consolidation decisions for later improvement",
|
|
535
|
+
"memory.features.embedRoute": "Semantic search route",
|
|
536
|
+
"memory.features.embedProvider": "Embedding provider",
|
|
537
|
+
"memory.features.embedProvider.openai": "OpenAI-compatible API",
|
|
538
|
+
"memory.features.embedProvider.local": "Local model (offline)",
|
|
539
|
+
"memory.features.embedProvider.ollama": "Ollama",
|
|
540
|
+
"memory.features.embedProvider.openai.hint": "Endpoint / key / model live in the Vector Search card below",
|
|
541
|
+
"memory.features.embedProvider.local.hint": "First use downloads the model; fully offline afterwards",
|
|
542
|
+
"memory.features.embedProvider.ollama.hint": "Requires a local Ollama server",
|
|
543
|
+
"memory.features.localEmbedModel": "Local embedding model",
|
|
544
|
+
"memory.features.ollamaBaseUrl": "Ollama server URL",
|
|
545
|
+
"memory.features.ollamaModel": "Ollama model",
|
|
546
|
+
"memory.features.dreamProvider": "Consolidation provider",
|
|
547
|
+
"memory.features.dreamModel": "Consolidation model",
|
|
548
|
+
"memory.features.dreamModelHint": "Leave empty to follow the main conversation model; only affects autoDream consolidation",
|
|
549
|
+
"memory.explorer.viewCards": "Cards",
|
|
550
|
+
"memory.explorer.viewTimeline": "Timeline",
|
|
551
|
+
"memory.explorer.viewAria": "Switch view",
|
|
552
|
+
"memory.explorer.dateLabel": "Time",
|
|
553
|
+
"memory.explorer.date.all": "All time",
|
|
554
|
+
"memory.explorer.date.7d": "Last 7 days",
|
|
555
|
+
"memory.explorer.date.30d": "Last 30 days",
|
|
556
|
+
"memory.explorer.date.90d": "Last 90 days",
|
|
557
|
+
"memory.explorer.more": "More actions",
|
|
558
|
+
"memory.explorer.exportJson": "Export JSON",
|
|
559
|
+
"memory.explorer.exportMarkdown": "Export Markdown",
|
|
560
|
+
"memory.explorer.importMd": "Import Markdown mirror…",
|
|
561
|
+
"memory.explorer.importTitle": "Import Markdown mirror",
|
|
562
|
+
"memory.explorer.importHint": "Pick a Markdown mirror file; its manual edits are parsed and merged into the store.",
|
|
563
|
+
"memory.explorer.importType": "Mirror type",
|
|
564
|
+
"memory.explorer.importPick": "Choose file…",
|
|
565
|
+
"memory.explorer.importConfirm": "Import",
|
|
566
|
+
"memory.explorer.importCancel": "Cancel",
|
|
567
|
+
"memory.explorer.importing": "Importing…",
|
|
568
|
+
"memory.explorer.imported": "Merged {n} edits",
|
|
569
|
+
"memory.explorer.importFailed": "Import failed",
|
|
570
|
+
"memory.explorer.importNoFile": "Choose a file first",
|
|
571
|
+
"memory.explorer.exportFailed": "Export failed",
|
|
572
|
+
"memory.explorer.conflictBadge": "Conflict",
|
|
573
|
+
"memory.explorer.archivedBadge": "Archived",
|
|
574
|
+
"memory.explorer.detail.edit": "Edit",
|
|
575
|
+
"memory.explorer.detail.save": "Save",
|
|
576
|
+
"memory.explorer.detail.cancel": "Cancel",
|
|
577
|
+
"memory.explorer.detail.archive": "Archive",
|
|
578
|
+
"memory.explorer.detail.archived": "Archived",
|
|
579
|
+
"memory.explorer.detail.archiveFailed": "Action failed",
|
|
580
|
+
"memory.explorer.detail.saved": "Saved; mirrors re-rendered",
|
|
581
|
+
"memory.explorer.detail.quality": "Quality",
|
|
582
|
+
"memory.explorer.detail.entities": "Related entities",
|
|
583
|
+
"memory.explorer.detail.entitiesEmpty": "Turn on entity extraction and related people / projects appear here",
|
|
584
|
+
"memory.explorer.detail.closeAria": "Close details",
|
|
585
|
+
"memory.explorer.detail.editTitle": "Title",
|
|
586
|
+
"memory.explorer.detail.editContent": "Content",
|
|
587
|
+
"memory.explorer.detail.editImportance": "Importance",
|
|
588
|
+
"memory.status.dream": "Last consolidation",
|
|
589
|
+
"memory.status.dreamNever": "Not yet run",
|
|
590
|
+
"memory.status.conflicts": "Pending conflicts",
|
|
591
|
+
"memory.status.conflictsHint": "Frozen contradictory memories awaiting confirmation",
|
|
592
|
+
"memory.status.workbench": "Activity",
|
|
593
|
+
"memory.status.dreamConsolidate": "Consolidation",
|
|
594
|
+
"memory.status.summarize": "Summarization",
|
|
595
|
+
"memory.status.tokens": "{n} tokens",
|
|
596
|
+
"memory.status.deposited": "{n} memories deposited",
|
|
597
|
+
"memory.status.failed": "failed",
|
|
598
|
+
"memory.status.success": "success",
|
|
599
|
+
"memory.status.skipped": "skipped",
|
|
600
|
+
"memory.status.emptyFeed": "No background activity yet: memories are distilled after conversations",
|
|
601
|
+
"memory.status.consolidated": "Deposited memories",
|
|
602
|
+
"memory.status.consolidatedEmpty": "Memories deposited by autoDream / autoSummarize appear here",
|
|
603
|
+
"memory.status.archivedMemories": "Archived memories",
|
|
604
|
+
"memory.status.archivedEmpty": "Nothing archived",
|
|
605
|
+
"memory.status.restore": "Restore",
|
|
606
|
+
"memory.settings.extapi.reveal": "Reveal",
|
|
607
|
+
"memory.settings.extapi.hide": "Hide",
|
|
608
|
+
"memory.settings.extapi.maskHint": "Token masked; Copy still copies the full value",
|
|
349
609
|
"memory.explorer.delete": "Delete",
|
|
350
610
|
"memory.explorer.confirmDelete": "Confirm delete?",
|
|
351
611
|
"memory.explorer.cancel": "Cancel",
|
|
@@ -428,18 +688,18 @@ window.__ModuleLoader__.load({
|
|
|
428
688
|
".mneme-entitychip:hover{background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 10%,transparent)}",
|
|
429
689
|
// --- main-area memory library page ---
|
|
430
690
|
".mneme-x{flex:1;min-height:0;height:100%;width:100%;box-sizing:border-box;display:flex;flex-direction:column;background:var(--dsw-alias-bg-layer-1);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2)}",
|
|
431
|
-
".mneme-xbar{flex:none;display:flex;align-items:center;gap:6px;border-bottom:1px solid var(--dsw-alias-border-l2);padding:0 16px}",
|
|
691
|
+
".mneme-xbar{position:relative;flex:none;display:flex;align-items:center;justify-content:center;gap:6px;border-bottom:1px solid var(--dsw-alias-border-l2);padding:0 16px;min-height:52px}",
|
|
432
692
|
".mneme-filterbar{flex:none;display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:8px 16px;border-bottom:1px solid var(--dsw-alias-border-l2)}",
|
|
433
|
-
".mneme-vtabs{display:flex;align-items:stretch;height:
|
|
434
|
-
".mneme-vtab{position:relative;border:0;background:none;cursor:pointer;padding:0
|
|
693
|
+
".mneme-vtabs{display:flex;align-items:stretch;height:46px}",
|
|
694
|
+
".mneme-vtab{position:relative;border:0;background:none;cursor:pointer;padding:0 16px;color:var(--dsw-alias-label-tertiary);font-family:inherit;font-size:14px;font-weight:500;line-height:20px;display:inline-flex;align-items:center;gap:7px}",
|
|
435
695
|
".mneme-vtab:hover{color:var(--dsw-alias-label-primary)}",
|
|
436
696
|
".mneme-vtab.mneme-active{color:var(--dsw-alias-state-business-primary)}",
|
|
437
|
-
".mneme-vtab.mneme-active::after{content:\"\";position:absolute;left:
|
|
438
|
-
".mneme-xtools{
|
|
697
|
+
".mneme-vtab.mneme-active::after{content:\"\";position:absolute;left:10px;right:10px;bottom:-1px;height:2.5px;border-radius:2px;background:var(--dsw-alias-state-business-primary)}",
|
|
698
|
+
".mneme-xtools{position:absolute;right:14px;top:50%;transform:translateY(-50%);display:flex;align-items:center;gap:8px;padding:0}",
|
|
439
699
|
".mneme-xcount{flex:none;font-size:12px;line-height:16px;color:var(--dsw-alias-label-tertiary);white-space:nowrap}",
|
|
440
700
|
// --- three-column browse layout: hairline separators, no outer box ---
|
|
441
701
|
".mneme-xmain{flex:1;min-height:0;display:flex;flex-direction:row;overflow:hidden}",
|
|
442
|
-
".mneme-xside{flex:none;width:236px;min-width:0;min-height:0;overflow-y:auto;padding:
|
|
702
|
+
".mneme-xside{flex:none;width:236px;min-width:0;min-height:0;overflow-y:auto;padding:16px 14px;border-right:1px solid var(--dsw-alias-border-l2);box-sizing:border-box;display:flex;flex-direction:column;gap:8px}",
|
|
443
703
|
".mneme-xside--filter{width:214px}",
|
|
444
704
|
".mneme-xbrowse{flex:1;min-width:0;min-height:0;display:flex;flex-direction:column;overflow:hidden}",
|
|
445
705
|
".mneme-xrow{display:flex;align-items:center;gap:8px;flex-wrap:wrap}",
|
|
@@ -525,7 +785,7 @@ window.__ModuleLoader__.load({
|
|
|
525
785
|
".mneme-relname{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
526
786
|
// --- settings: quiet stacked sections, one concern per section ---
|
|
527
787
|
".mneme-set{flex:1;min-height:0;overflow-y:auto;padding:8px 24px 48px;box-sizing:border-box}",
|
|
528
|
-
".mneme-set-inner{max-width:
|
|
788
|
+
".mneme-set-inner{max-width:720px;margin:0 auto}",
|
|
529
789
|
".mneme-set-sec{padding:20px 0 24px;border-bottom:1px solid var(--dsw-alias-border-l2)}",
|
|
530
790
|
".mneme-set-sec:last-child{border-bottom:none}",
|
|
531
791
|
".mneme-set-title{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary);margin-bottom:4px}",
|
|
@@ -550,16 +810,95 @@ window.__ModuleLoader__.load({
|
|
|
550
810
|
".mneme-set-cmddesc{font-size:12px;line-height:17px;color:var(--dsw-alias-label-tertiary);margin-top:1px;word-break:break-word}",
|
|
551
811
|
// --- settings sub-view ---
|
|
552
812
|
".mneme-set{flex:1;min-height:0;overflow-y:auto;padding:8px 24px 48px;box-sizing:border-box}",
|
|
553
|
-
// ---
|
|
554
|
-
//
|
|
555
|
-
//
|
|
556
|
-
//
|
|
557
|
-
|
|
558
|
-
".mneme-overlay{position:fixed;
|
|
559
|
-
".mneme-overlaybar{flex:none;display:flex;align-items:center;justify-content:space-between;height:
|
|
560
|
-
".mneme-overlaytitle{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary)}",
|
|
813
|
+
// --- 记忆库页面:居中 sheet,非全屏 ---
|
|
814
|
+
// 从对话直接打开一页记忆库:背板压暗 + 居中圆角卡片(上限 1180×880),
|
|
815
|
+
// 对话仍留在背板之后,任何状态下(含新会话 hero、无 tab 环)都可用。
|
|
816
|
+
// portal 到 <body>,宿主侧边栏的层叠上下文裁不住它。
|
|
817
|
+
".mneme-backdrop{position:fixed;inset:0;z-index:999;background:color-mix(in srgb,var(--dsw-alias-label-primary) 16%,transparent);backdrop-filter:blur(2px);animation:mneme-fadein .14s ease-out}",
|
|
818
|
+
".mneme-overlay{position:fixed;z-index:1000;left:50%;top:50%;transform:translate(-50%,-50%);width:min(1240px,calc(100vw - 88px));height:min(920px,calc(100vh - 64px));display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2);border-radius:16px;overflow:hidden;background:var(--dsw-alias-bg-layer-1);box-shadow:0 24px 64px color-mix(in srgb,var(--dsw-alias-label-primary) 22%,transparent);animation:mneme-pop .18s cubic-bezier(.2,.9,.3,1)}",
|
|
819
|
+
".mneme-overlaybar{flex:none;display:flex;align-items:center;justify-content:space-between;height:48px;padding:0 12px 0 18px;border-bottom:1px solid var(--dsw-alias-border-l2)}",
|
|
820
|
+
".mneme-overlaytitle{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;gap:8px}",
|
|
561
821
|
".mneme-overlaybody{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}",
|
|
562
822
|
"@keyframes mneme-fadein{from{opacity:0}to{opacity:1}}",
|
|
823
|
+
"@keyframes mneme-pop{from{opacity:0;transform:translate(-50%,-50%) scale(.98)}to{opacity:1;transform:translate(-50%,-50%) scale(1)}}",
|
|
824
|
+
// --- 侧边栏顶部入口:借宿主「新会话」按钮的原生类名对齐 ---
|
|
825
|
+
// wrapper display:contents 隐身,按钮成为侧边栏弹性布局的直接子元素;
|
|
826
|
+
// 盒模型/间距/收起态 rail 几何全部继承宿主,我们只覆盖配色为次级观感。
|
|
827
|
+
".mneme-topentry{display:contents}",
|
|
828
|
+
".mneme-topentry-native{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary)}",
|
|
829
|
+
".mneme-topentry-native:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}",
|
|
830
|
+
".mneme-topentry-native .mneme-topentry-label{flex:1;min-width:0;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
|
|
831
|
+
// --- 功能开关:一行一开关,Claude 式安静排版 ---
|
|
832
|
+
".mneme-featgroup{flex:none;font-size:12px;font-weight:600;letter-spacing:.02em;color:var(--dsw-alias-label-tertiary);margin:16px 0 2px}",
|
|
833
|
+
".mneme-featrow{display:flex;align-items:center;gap:14px;padding:11px 2px;border-bottom:1px solid var(--dsw-alias-border-l1)}",
|
|
834
|
+
".mneme-featrow:last-child{border-bottom:none}",
|
|
835
|
+
".mneme-featmain{flex:1;min-width:0}",
|
|
836
|
+
".mneme-featname{font-size:13.5px;font-weight:500;line-height:20px;color:var(--dsw-alias-label-primary)}",
|
|
837
|
+
".mneme-feathint{font-size:12.5px;line-height:18px;color:var(--dsw-alias-label-tertiary);margin-top:2px}",
|
|
838
|
+
".mneme-switch{position:relative;flex:none;width:36px;height:22px;border-radius:11px;border:none;cursor:pointer;background:var(--dsw-alias-interactive-bg-active);transition:background .15s}",
|
|
839
|
+
".mneme-switch::after{content:\"\";position:absolute;top:2px;left:2px;width:18px;height:18px;border-radius:50%;background:var(--dsw-alias-bg-layer-1,#fff);box-shadow:0 1px 3px color-mix(in srgb,var(--dsw-alias-label-primary) 25%,transparent);transition:transform .15s}",
|
|
840
|
+
".mneme-switch.mneme-on{background:var(--dsw-alias-state-business-primary)}",
|
|
841
|
+
".mneme-switch.mneme-on::after{transform:translateX(14px)}",
|
|
842
|
+
".mneme-switch:disabled{opacity:.5;cursor:default}",
|
|
843
|
+
".mneme-featnum{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 2px;border-bottom:1px solid var(--dsw-alias-border-l1)}",
|
|
844
|
+
".mneme-featnum:last-child{border-bottom:none}",
|
|
845
|
+
".mneme-featnumlabel{font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}",
|
|
846
|
+
".mneme-numinput{box-sizing:border-box;width:110px;height:30px;padding:0 10px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base,transparent);color:var(--dsw-alias-label-primary);font-family:inherit;font-size:13px;outline:none;text-align:right;transition:border-color .12s,box-shadow .12s}",
|
|
847
|
+
".mneme-numinput:focus{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--dsw-alias-state-business-primary) 15%,transparent)}",
|
|
848
|
+
// 字符串开关的输入框(provider/model 等)与其子块容器
|
|
849
|
+
".mneme-strinput{box-sizing:border-box;width:240px;max-width:60%;height:30px;padding:0 10px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base,transparent);color:var(--dsw-alias-label-primary);font-family:inherit;font-size:13px;outline:none;text-align:left;transition:border-color .12s,box-shadow .12s}",
|
|
850
|
+
".mneme-strinput:focus{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--dsw-alias-state-business-primary) 15%,transparent)}",
|
|
851
|
+
".mneme-featsub{margin:2px 0 8px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;display:flex;flex-direction:column;gap:6px}",
|
|
852
|
+
".mneme-featsub .mneme-featnum{padding:6px 0;border-bottom:none}",
|
|
853
|
+
".mneme-featsubhint{font-size:12px;line-height:17px;color:var(--dsw-alias-label-tertiary)}",
|
|
854
|
+
// --- 交互式记忆库:工具栏 / 卡片网格 / 详情抽屉 / 更多菜单 ---
|
|
855
|
+
// sheet 边距放宽:与窗口边缘保持呼吸距离,居中不顶满。
|
|
856
|
+
// 视图切换(卡片/时间线)分段控件挂在子页栏右侧。
|
|
857
|
+
".mneme-seg{display:inline-flex;align-items:center;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;overflow:hidden}",
|
|
858
|
+
".mneme-seg button{border:none;background:none;color:var(--dsw-alias-label-tertiary);cursor:pointer;font-family:inherit;font-size:12px;line-height:16px;padding:5px 10px;display:inline-flex;align-items:center;gap:5px}",
|
|
859
|
+
".mneme-seg button:hover{color:var(--dsw-alias-label-primary)}",
|
|
860
|
+
".mneme-seg button.mneme-active{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary);font-weight:500}",
|
|
861
|
+
// 卡片网格:响应式 auto-fill,卡片悬停轻浮起
|
|
862
|
+
".mneme-cards{flex:1;min-height:0;overflow-y:auto;display:grid;grid-template-columns:repeat(auto-fill,minmax(252px,1fr));gap:14px;padding:20px 24px 32px;align-content:start}",
|
|
863
|
+
".mneme-card{display:flex;flex-direction:column;gap:7px;min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:12px 13px;background:var(--dsw-alias-bg-layer-1);cursor:pointer;font-family:inherit;text-align:left;transition:border-color .12s,box-shadow .12s,transform .12s}",
|
|
864
|
+
".mneme-card:hover{border-color:color-mix(in srgb,var(--dsw-alias-state-business-primary) 45%,var(--dsw-alias-border-l2));box-shadow:0 6px 20px color-mix(in srgb,var(--dsw-alias-label-primary) 10%,transparent);transform:translateY(-1px)}",
|
|
865
|
+
".mneme-card.mneme-active{border-color:var(--dsw-alias-state-business-primary)}",
|
|
866
|
+
".mneme-cardhead{display:flex;align-items:center;gap:6px;font-size:11.5px;line-height:16px;color:var(--dsw-alias-label-tertiary)}",
|
|
867
|
+
".mneme-cardtitle{font-size:13.5px;font-weight:600;line-height:19px;color:var(--dsw-alias-label-primary);word-break:break-word;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}",
|
|
868
|
+
".mneme-cardexcerpt{font-size:12.5px;line-height:18px;color:var(--dsw-alias-label-secondary);word-break:break-word;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}",
|
|
869
|
+
".mneme-cardfoot{display:flex;align-items:center;gap:8px;margin-top:auto;font-size:11.5px;line-height:16px;color:var(--dsw-alias-label-tertiary)}",
|
|
870
|
+
".mneme-cardsrc{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
871
|
+
// 徽章:冲突待确认 / 已归档
|
|
872
|
+
".mneme-badge{flex:none;display:inline-flex;align-items:center;gap:4px;height:18px;padding:0 7px;border-radius:6px;font-size:11px;line-height:14px}",
|
|
873
|
+
".mneme-badge--conflict{color:var(--dsw-alias-state-error,#c33);background:color-mix(in srgb,var(--dsw-alias-state-error,#c33) 10%,transparent);border:1px solid color-mix(in srgb,var(--dsw-alias-state-error,#c33) 30%,transparent)}",
|
|
874
|
+
".mneme-badge--archived{color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
875
|
+
// 详情抽屉:sheet 内右侧滑出,覆盖在浏览区之上
|
|
876
|
+
".mneme-xmain{position:relative}",
|
|
877
|
+
"@keyframes mneme-slidein{from{opacity:0;transform:translateX(16px)}to{opacity:1;transform:translateX(0)}}",
|
|
878
|
+
".mneme-drawer{position:absolute;top:0;right:0;bottom:0;width:min(432px,52%);display:flex;flex-direction:column;border-left:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);box-shadow:-16px 0 40px color-mix(in srgb,var(--dsw-alias-label-primary) 10%,transparent);animation:mneme-slidein .18s ease-out;z-index:6}",
|
|
879
|
+
".mneme-drawerbar{flex:none;display:flex;align-items:center;gap:8px;height:44px;padding:0 10px 0 16px;border-bottom:1px solid var(--dsw-alias-border-l2)}",
|
|
880
|
+
".mneme-drawertype{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;font-weight:500;color:var(--dsw-alias-label-secondary)}",
|
|
881
|
+
".mneme-drawerbody{flex:1;min-height:0;overflow-y:auto;padding:20px 22px 28px}",
|
|
882
|
+
".mneme-drawertitle{width:100%;font-size:16px;font-weight:600;line-height:23px;color:var(--dsw-alias-label-primary);word-break:break-word}",
|
|
883
|
+
".mneme-drawertitle-input{width:100%;box-sizing:border-box;font-size:15px;font-weight:600;line-height:22px;padding:7px 10px;border-radius:8px;border:1px solid var(--dsw-alias-state-business-primary);background:var(--dsw-alias-bg-base,transparent);color:var(--dsw-alias-label-primary);font-family:inherit;outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--dsw-alias-state-business-primary) 15%,transparent)}",
|
|
884
|
+
".mneme-dmeta{display:grid;grid-template-columns:auto 1fr;gap:4px 14px;margin-top:12px;font-size:12.5px;line-height:19px}",
|
|
885
|
+
".mneme-dmetakey{color:var(--dsw-alias-label-tertiary)}",
|
|
886
|
+
".mneme-dmetaval{color:var(--dsw-alias-label-secondary);min-width:0;word-break:break-word}",
|
|
887
|
+
".mneme-dcontent{margin-top:14px;font-size:13.5px;line-height:1.75;color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word}",
|
|
888
|
+
".mneme-dcontent-input{width:100%;box-sizing:border-box;min-height:180px;font-size:13.5px;line-height:1.7;padding:9px 11px;border-radius:8px;border:1px solid var(--dsw-alias-state-business-primary);background:var(--dsw-alias-bg-base,transparent);color:var(--dsw-alias-label-primary);font-family:inherit;outline:none;resize:vertical;box-shadow:0 0 0 3px color-mix(in srgb,var(--dsw-alias-state-business-primary) 15%,transparent)}",
|
|
889
|
+
".mneme-dentities{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}",
|
|
890
|
+
".mnementity-chip{display:inline-flex;align-items:center;gap:5px;height:24px;padding:0 9px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;font-size:12px;line-height:16px;color:var(--dsw-alias-label-secondary);cursor:default}",
|
|
891
|
+
".mneme-dactions{flex:none;display:flex;flex-wrap:wrap;gap:8px;padding:10px 16px;border-top:1px solid var(--dsw-alias-border-l2)}",
|
|
892
|
+
// 更多操作菜单(导入/导出)与导入弹层
|
|
893
|
+
".mneme-menuwrap{position:relative}",
|
|
894
|
+
".mneme-menu{position:absolute;right:0;top:calc(100% + 6px);min-width:180px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-1);box-shadow:0 12px 32px color-mix(in srgb,var(--dsw-alias-label-primary) 16%,transparent);padding:5px;z-index:20;animation:mneme-fadein .1s ease-out}",
|
|
895
|
+
".mneme-menu button{display:flex;align-items:center;gap:8px;width:100%;border:none;background:none;color:var(--dsw-alias-label-primary);cursor:pointer;font-family:inherit;font-size:13px;line-height:18px;padding:8px 10px;border-radius:8px;text-align:left}",
|
|
896
|
+
".mneme-menu button:hover{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
897
|
+
".mneme-impdialog{position:absolute;right:0;top:calc(100% + 6px);width:300px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);box-shadow:0 12px 32px color-mix(in srgb,var(--dsw-alias-label-primary) 16%,transparent);padding:14px;z-index:20;animation:mneme-fadein .1s ease-out}",
|
|
898
|
+
".mneme-improw{display:flex;align-items:center;gap:8px;margin-top:10px}",
|
|
899
|
+
// 瞬时操作提示(删除/归档):挂在浏览层底部居中,不被抽屉卸载吞掉
|
|
900
|
+
".mneme-x{position:relative}",
|
|
901
|
+
".mneme-toast{position:absolute;left:50%;bottom:20px;transform:translateX(-50%);background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-1);font-size:12.5px;line-height:18px;padding:7px 16px;border-radius:10px;z-index:30;box-shadow:0 8px 24px color-mix(in srgb,var(--dsw-alias-label-primary) 25%,transparent);animation:mneme-fadein .15s ease-out}",
|
|
563
902
|
// --- explorer refresh: paged month tree, sticky headers, inline icons ---
|
|
564
903
|
".mneme-vtabico{flex:none;opacity:.85}",
|
|
565
904
|
".mneme-xsearchwrap{position:relative;flex:none}",
|
|
@@ -572,9 +911,21 @@ window.__ModuleLoader__.load({
|
|
|
572
911
|
".mneme-xmore{display:flex;justify-content:center;align-items:center;padding:10px 0 24px;min-height:20px}",
|
|
573
912
|
".mneme-xemptyico{display:block;margin:0 auto 6px;opacity:.7}",
|
|
574
913
|
// --- status sub-view: responsive stat-card grid (auto-fill, ~220px min) ---
|
|
575
|
-
".mneme-status{flex:1;min-height:0;overflow-y:auto;padding:
|
|
576
|
-
".mneme-statusgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px;max-width:
|
|
914
|
+
".mneme-status{flex:1;min-height:0;overflow-y:auto;padding:20px 24px 40px;box-sizing:border-box}",
|
|
915
|
+
".mneme-statusgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px;max-width:1000px;margin:0 auto}",
|
|
577
916
|
".mneme-statuscard{min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:14px}",
|
|
917
|
+
// --- 状态页工作台:让用户看见插件在干活(动态/沉淀/归档) ---
|
|
918
|
+
".mneme-wbhead{margin:28px auto 10px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);max-width:1000px}",
|
|
919
|
+
".mneme-wblist{max-width:1000px;display:flex;flex-direction:column;gap:6px}",
|
|
920
|
+
".mneme-wbrow{display:flex;gap:10px;align-items:flex-start;padding:10px 14px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px}",
|
|
921
|
+
".mneme-wbdot{flex:none;width:8px;height:8px;border-radius:50%;margin-top:6px;background:var(--dsw-alias-state-success,#2a9d6a)}",
|
|
922
|
+
".mneme-wbdot--err{background:var(--dsw-alias-state-error,#c33)}",
|
|
923
|
+
".mneme-wbmain{min-width:0;flex:1}",
|
|
924
|
+
".mneme-wbtitle{font-size:13px;line-height:19px;color:var(--dsw-alias-label-primary)}",
|
|
925
|
+
".mneme-wbsub{font-size:12px;line-height:17px;color:var(--dsw-alias-label-tertiary);margin-top:1px}",
|
|
926
|
+
".mneme-wbmemo{display:flex;gap:10px;align-items:center;padding:10px 14px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px}",
|
|
927
|
+
".mneme-wbmemo .mneme-xdot{flex:none}",
|
|
928
|
+
".mneme-wbmemo-main{min-width:0;flex:1}",
|
|
578
929
|
".mneme-statusnum{font-size:24px;font-weight:600;line-height:32px;color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums}",
|
|
579
930
|
".mneme-statuscap{margin-top:4px;font-size:13px;line-height:19px;color:var(--dsw-alias-label-tertiary);word-break:break-word}",
|
|
580
931
|
// --- destructive actions: red outline = delete, solid red = confirm ---
|
|
@@ -623,7 +974,12 @@ window.__ModuleLoader__.load({
|
|
|
623
974
|
project: "#22c55e",
|
|
624
975
|
decision: "#3b82f6",
|
|
625
976
|
summary: "#a855f7",
|
|
626
|
-
history: "#94a3b8"
|
|
977
|
+
history: "#94a3b8",
|
|
978
|
+
// codingRetrospect 的三类编码记忆:警示色区分度最高——踩坑/被否决方案
|
|
979
|
+
// 天然带"注意"语义,工程约束用冷色表达"规则"。
|
|
980
|
+
rejected_solution: "#fb923c",
|
|
981
|
+
pitfall: "#ef4444",
|
|
982
|
+
constraint: "#0ea5e9"
|
|
627
983
|
};
|
|
628
984
|
function memoryTypeColor(type) {
|
|
629
985
|
return MEMORY_TYPE_COLORS[type] || "#94a3b8";
|
|
@@ -1110,6 +1466,185 @@ window.__ModuleLoader__.load({
|
|
|
1110
1466
|
}
|
|
1111
1467
|
|
|
1112
1468
|
|
|
1469
|
+
// --- 功能开关卡片(可插拔后端能力)---
|
|
1470
|
+
// GET /features 拿 overrides + effective,PUT 提交增量;启动时 index.js
|
|
1471
|
+
// 把持久化的 kv 合并进配置,因此每个开关都标注「重启 DSH 后生效」。
|
|
1472
|
+
// 分组排版:核心/增强/巩固常驻,注入策略等收进「高级」折叠,普通用户
|
|
1473
|
+
// 不被专业项淹没。429 调速器参数、distillMaxChars、codingBoostFactor
|
|
1474
|
+
// 属调优噪音,按对齐结论留在配置文件,不上 UI。
|
|
1475
|
+
const FEATURE_GROUPS = [
|
|
1476
|
+
{ key: "group.core", items: ["autoInject", "autoSummarize", "hotMemoryEnabled", "memoryQualityFilter.enabled", "llmAudit.enabled"] },
|
|
1477
|
+
{ key: "group.enhance", items: ["entityExtractionEnabled", "codingRetrospect", "rerankEnabled", "searchSemanticDedup", "bm25SearchEnabled"] },
|
|
1478
|
+
{ key: "group.dream", items: ["autoDream", "sleepModeEnabled"] }
|
|
1479
|
+
];
|
|
1480
|
+
const FEATURE_ADVANCED_BOOLS = ["hybridInject", "selectiveInjectEnabled", "adaptiveThresholdEnabled", "reflectionUpdateEnabled", "reflectionFailureTracking", "conflictFreezeEnabled", "trustEpistemicWeighting"];
|
|
1481
|
+
// 字符串键(blur/Enter 提交,空串合法 = 跟随默认):巩固模型与语义
|
|
1482
|
+
// 检索路线。embedProvider 是枚举,用下拉单独渲染。
|
|
1483
|
+
const FEATURE_STRINGS = ["dreamProvider", "dreamModel", "localEmbedModel", "ollamaBaseUrl", "ollamaModel"];
|
|
1484
|
+
const EMBED_PROVIDERS = ["openai", "local", "ollama"];
|
|
1485
|
+
|
|
1486
|
+
function FeatureRow({ name, hint, on, disabled, onToggle }) {
|
|
1487
|
+
return h("div", { className: "mneme-featrow" },
|
|
1488
|
+
h("div", { className: "mneme-featmain" },
|
|
1489
|
+
h("div", { className: "mneme-featname" }, name),
|
|
1490
|
+
h("div", { className: "mneme-feathint" }, hint)
|
|
1491
|
+
),
|
|
1492
|
+
h("button", {
|
|
1493
|
+
type: "button",
|
|
1494
|
+
className: on ? "mneme-switch mneme-on" : "mneme-switch",
|
|
1495
|
+
role: "switch",
|
|
1496
|
+
"aria-checked": String(on),
|
|
1497
|
+
"aria-label": name,
|
|
1498
|
+
disabled,
|
|
1499
|
+
onClick: onToggle
|
|
1500
|
+
})
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
function FeaturesCard({ t }) {
|
|
1505
|
+
const [state, setState] = useState(null); // { overrides, effective }
|
|
1506
|
+
const [error, setError] = useState("");
|
|
1507
|
+
const [busy, setBusy] = useState(false);
|
|
1508
|
+
const [savedTick, setSavedTick] = useState(false);
|
|
1509
|
+
const [showAdv, setShowAdv] = useState(false);
|
|
1510
|
+
const [strs, setStrs] = useState({}); // 字符串输入的本地草稿:key -> string
|
|
1511
|
+
|
|
1512
|
+
useEffect(() => {
|
|
1513
|
+
let cancelled = false;
|
|
1514
|
+
apiFetch("/api/dsh-mneme/features")
|
|
1515
|
+
.then((res) => { if (!res.ok) throw new Error("HTTP " + res.status); return res.json(); })
|
|
1516
|
+
.then((j) => {
|
|
1517
|
+
if (cancelled) return;
|
|
1518
|
+
setState(j);
|
|
1519
|
+
const e = (j && j.effective) || {};
|
|
1520
|
+
const draft = {};
|
|
1521
|
+
for (const key of FEATURE_STRINGS) draft[key] = String(e[key] ?? "");
|
|
1522
|
+
setStrs(draft);
|
|
1523
|
+
})
|
|
1524
|
+
.catch(() => { if (!cancelled) setError(t("memory.features.loadFailed")); });
|
|
1525
|
+
return () => { cancelled = true; };
|
|
1526
|
+
}, [t]);
|
|
1527
|
+
|
|
1528
|
+
const eff = (state && state.effective) || {};
|
|
1529
|
+
|
|
1530
|
+
const put = async (patch) => {
|
|
1531
|
+
setBusy(true);
|
|
1532
|
+
try {
|
|
1533
|
+
const res = await apiFetch("/api/dsh-mneme/features", {
|
|
1534
|
+
method: "PUT",
|
|
1535
|
+
headers: { "Content-Type": "application/json" },
|
|
1536
|
+
body: JSON.stringify(patch)
|
|
1537
|
+
});
|
|
1538
|
+
if (!res.ok) throw new Error("HTTP " + res.status);
|
|
1539
|
+
setState(await res.json());
|
|
1540
|
+
setSavedTick(true);
|
|
1541
|
+
setTimeout(() => setSavedTick(false), 1800);
|
|
1542
|
+
} catch {
|
|
1543
|
+
setError(t("memory.features.loadFailed"));
|
|
1544
|
+
} finally {
|
|
1545
|
+
setBusy(false);
|
|
1546
|
+
}
|
|
1547
|
+
};
|
|
1548
|
+
|
|
1549
|
+
// 字符串项失焦/回车提交:trim 后与生效值相同则回填放弃,否则交给
|
|
1550
|
+
// 后端校验(ollamaBaseUrl 的协议、embedProvider 的枚举在后端把关)。
|
|
1551
|
+
const commitString = (key) => {
|
|
1552
|
+
const raw = (strs[key] ?? "").trim();
|
|
1553
|
+
if (raw === String(eff[key] ?? "")) {
|
|
1554
|
+
setStrs((c) => ({ ...c, [key]: String(eff[key] ?? "") }));
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
put({ [key]: raw });
|
|
1558
|
+
};
|
|
1559
|
+
|
|
1560
|
+
const boolRow = (k) => h(FeatureRow, {
|
|
1561
|
+
key: k,
|
|
1562
|
+
name: t(`memory.features.${k}`),
|
|
1563
|
+
hint: t(`memory.features.${k}.hint`),
|
|
1564
|
+
on: !!eff[k],
|
|
1565
|
+
disabled: busy,
|
|
1566
|
+
onToggle: () => put({ [k]: !eff[k] })
|
|
1567
|
+
});
|
|
1568
|
+
|
|
1569
|
+
const strRow = (key) => h("div", { className: "mneme-featnum", key },
|
|
1570
|
+
h("span", { className: "mneme-featnumlabel" }, t(`memory.features.${key}`)),
|
|
1571
|
+
h("input", {
|
|
1572
|
+
className: "mneme-strinput",
|
|
1573
|
+
value: strs[key] ?? "",
|
|
1574
|
+
onChange: (e) => setStrs((c) => ({ ...c, [key]: e.target.value })),
|
|
1575
|
+
onBlur: () => commitString(key),
|
|
1576
|
+
onKeyDown: (e) => { if (e.key === "Enter") e.target.blur(); }
|
|
1577
|
+
}));
|
|
1578
|
+
|
|
1579
|
+
// 语义检索路线:provider 下拉即时提交;local/ollama 选中时展开各自
|
|
1580
|
+
// 的连接字段,openai 的连接信息由「向量搜索」卡片持有,只给指引。
|
|
1581
|
+
const embedSub = h("div", { className: "mneme-featsub" },
|
|
1582
|
+
h("div", { className: "mneme-featnum" },
|
|
1583
|
+
h("span", { className: "mneme-featnumlabel" }, t("memory.features.embedProvider")),
|
|
1584
|
+
h("select", {
|
|
1585
|
+
className: "mneme-select",
|
|
1586
|
+
value: eff.embedProvider || "openai",
|
|
1587
|
+
disabled: busy,
|
|
1588
|
+
onChange: (e) => put({ embedProvider: e.target.value })
|
|
1589
|
+
},
|
|
1590
|
+
EMBED_PROVIDERS.map((p) => h("option", { key: p, value: p }, t(`memory.features.embedProvider.${p}`))))
|
|
1591
|
+
),
|
|
1592
|
+
h("div", { className: "mneme-featsubhint" },
|
|
1593
|
+
t(`memory.features.embedProvider.${eff.embedProvider || "openai"}.hint`)),
|
|
1594
|
+
eff.embedProvider === "local" && strRow("localEmbedModel"),
|
|
1595
|
+
eff.embedProvider === "ollama" && strRow("ollamaBaseUrl"),
|
|
1596
|
+
eff.embedProvider === "ollama" && strRow("ollamaModel")
|
|
1597
|
+
);
|
|
1598
|
+
|
|
1599
|
+
// 巩固模型:autoDream 开着才展开,避免闲置配置占版面。
|
|
1600
|
+
const dreamSub = eff.autoDream && h("div", { className: "mneme-featsub" },
|
|
1601
|
+
strRow("dreamProvider"),
|
|
1602
|
+
strRow("dreamModel"),
|
|
1603
|
+
h("div", { className: "mneme-featsubhint" }, t("memory.features.dreamModelHint"))
|
|
1604
|
+
);
|
|
1605
|
+
|
|
1606
|
+
if (error && !state) return h("section", { className: "mneme-set-card" },
|
|
1607
|
+
h("div", { className: "mneme-set-title" }, t("memory.features.title")),
|
|
1608
|
+
h("div", { className: "mneme-set-hint" }, error));
|
|
1609
|
+
|
|
1610
|
+
return h("section", { className: "mneme-set-card" },
|
|
1611
|
+
h("div", { className: "mneme-set-title" },
|
|
1612
|
+
t("memory.features.title"),
|
|
1613
|
+
savedTick && h("span", { className: "mneme-saved", style: { marginLeft: 8, fontWeight: 400 } }, t("memory.features.restartHint"))
|
|
1614
|
+
),
|
|
1615
|
+
h("div", { className: "mneme-set-desc" }, t("memory.features.desc")),
|
|
1616
|
+
state === null && !error
|
|
1617
|
+
? h("div", { className: "mneme-set-hint" }, "…")
|
|
1618
|
+
: h(react.Fragment, null,
|
|
1619
|
+
FEATURE_GROUPS.map((g) => h(react.Fragment, { key: g.key },
|
|
1620
|
+
h("div", { className: "mneme-featgroup" }, t(`memory.features.${g.key}`)),
|
|
1621
|
+
g.items.map(boolRow),
|
|
1622
|
+
g.key === "group.enhance" && embedSub,
|
|
1623
|
+
g.key === "group.dream" && dreamSub
|
|
1624
|
+
)),
|
|
1625
|
+
h("div", { className: "mneme-featgroup" },
|
|
1626
|
+
h("button", {
|
|
1627
|
+
type: "button",
|
|
1628
|
+
className: "mneme-footbtn",
|
|
1629
|
+
"aria-expanded": String(showAdv),
|
|
1630
|
+
onClick: () => setShowAdv(!showAdv)
|
|
1631
|
+
},
|
|
1632
|
+
h(Icon, { name: showAdv ? "chevronDown" : "chevronRight", size: 12 }),
|
|
1633
|
+
t("memory.features.advancedToggle"))
|
|
1634
|
+
),
|
|
1635
|
+
showAdv && FEATURE_ADVANCED_BOOLS.map(boolRow)
|
|
1636
|
+
)
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// 令牌遮蔽:保留首尾少量字符便于辨认,中间全部打点;完整值只经复制
|
|
1641
|
+
// 通道离开面板。
|
|
1642
|
+
function maskToken(value) {
|
|
1643
|
+
const s = String(value ?? "");
|
|
1644
|
+
if (s.length <= 8) return "•".repeat(Math.max(6, s.length));
|
|
1645
|
+
return `${s.slice(0, 5)}${"•".repeat(10)}${s.slice(-4)}`;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1113
1648
|
function SettingsContent({ t }) {
|
|
1114
1649
|
const [profile, setProfile] = react.useState("");
|
|
1115
1650
|
const [rules, setRules] = react.useState([]);
|
|
@@ -1141,6 +1676,8 @@ window.__ModuleLoader__.load({
|
|
|
1141
1676
|
const [extapiSaved, setExtapiSaved] = react.useState(false);
|
|
1142
1677
|
const [extapiCopied, setExtapiCopied] = react.useState(false);
|
|
1143
1678
|
const [extapiError, setExtapiError] = react.useState("");
|
|
1679
|
+
// 令牌默认遮蔽显示:完整值只经「复制」离开面板,不直接铺在页面上。
|
|
1680
|
+
const [extapiTokenShown, setExtapiTokenShown] = react.useState(false);
|
|
1144
1681
|
|
|
1145
1682
|
const load = react.useCallback(async () => {
|
|
1146
1683
|
try {
|
|
@@ -1433,6 +1970,9 @@ window.__ModuleLoader__.load({
|
|
|
1433
1970
|
)
|
|
1434
1971
|
)
|
|
1435
1972
|
),
|
|
1973
|
+
// 功能开关 — 0.7.13/0.7.14 后端能力的前端总闸(codingRetrospect、
|
|
1974
|
+
// 智能调速器参数、实体抽取、巩固等);重启 DSH 后生效。
|
|
1975
|
+
h(FeaturesCard, { t }),
|
|
1436
1976
|
// 运行模式 — light vs standard chip radios; each click PUTs and the
|
|
1437
1977
|
// change only lands after a DSH restart (green saved hint says so).
|
|
1438
1978
|
h("section", { className: "mneme-set-card" },
|
|
@@ -1505,7 +2045,16 @@ window.__ModuleLoader__.load({
|
|
|
1505
2045
|
t("memory.settings.vectorSave"))
|
|
1506
2046
|
),
|
|
1507
2047
|
h("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 10 } },
|
|
1508
|
-
h("div", {
|
|
2048
|
+
h("div", {
|
|
2049
|
+
className: "mneme-set-token",
|
|
2050
|
+
style: { flex: 1, minWidth: 0 },
|
|
2051
|
+
title: extapi.token && !extapiTokenShown ? t("memory.settings.extapi.maskHint") : undefined
|
|
2052
|
+
}, !extapi.token ? "—" : (extapiTokenShown ? extapi.token : maskToken(extapi.token))),
|
|
2053
|
+
h("button", {
|
|
2054
|
+
className: "mneme-footbtn",
|
|
2055
|
+
onClick: () => setExtapiTokenShown(!extapiTokenShown),
|
|
2056
|
+
"aria-label": extapiTokenShown ? t("memory.settings.extapi.hide") : t("memory.settings.extapi.reveal")
|
|
2057
|
+
}, extapiTokenShown ? t("memory.settings.extapi.hide") : t("memory.settings.extapi.reveal")),
|
|
1509
2058
|
h("button", { className: "mneme-btn", onClick: copyExtapiToken },
|
|
1510
2059
|
t("memory.settings.extapi.copy")),
|
|
1511
2060
|
extapiCopied && h("span", { className: "mneme-saved" }, t("memory.settings.extapi.copied"))
|
|
@@ -1551,59 +2100,14 @@ window.__ModuleLoader__.load({
|
|
|
1551
2100
|
);
|
|
1552
2101
|
}
|
|
1553
2102
|
|
|
1554
|
-
// ---
|
|
1555
|
-
//
|
|
1556
|
-
//
|
|
1557
|
-
//
|
|
1558
|
-
//
|
|
1559
|
-
//
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
// whole tab ring) is hidden while a session is blank — the new-chat hero
|
|
1563
|
-
// screen. There the tab simply does not exist, so activateExplorerTab
|
|
1564
|
-
// resolves false and the sidebar entry falls back to the full-viewport
|
|
1565
|
-
// overlay surface below.
|
|
1566
|
-
// Candidate tabs whose label matches ours exactly. The conversation tab
|
|
1567
|
-
// ring carries no registrant id in the DOM, so the label is all we have —
|
|
1568
|
-
// but other plugins may register views (or settings dialogs may render
|
|
1569
|
-
// panes) with the same text, and hidden panes still sit in the document.
|
|
1570
|
-
// Restrict to rendered tabs (offsetParent filters display:none subtrees)
|
|
1571
|
-
// and verify by rendering, not by label alone (see activateExplorerTab).
|
|
1572
|
-
function findExplorerTabs(label) {
|
|
1573
|
-
const matches = [];
|
|
1574
|
-
for (const tab of document.querySelectorAll('[role="tab"]')) {
|
|
1575
|
-
if ((tab.textContent || "").trim() !== label) continue;
|
|
1576
|
-
if (tab.offsetParent === null && getComputedStyle(tab).position !== "fixed") continue;
|
|
1577
|
-
matches.push(tab);
|
|
1578
|
-
}
|
|
1579
|
-
return matches;
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
// Click the memory library tab and wait (bounded, rAF-polled) until the
|
|
1583
|
-
// host marks it selected AND our explorer actually rendered (`.mneme-x`).
|
|
1584
|
-
// aria-selected alone cannot tell our tab from a same-labelled tab of
|
|
1585
|
-
// another plugin — the sidebar entry must not activate theirs. On a
|
|
1586
|
-
// failed candidate, move on to the next one; resolve false when the
|
|
1587
|
-
// fallback overlay should take over.
|
|
1588
|
-
function activateExplorerTab(label) {
|
|
1589
|
-
return new Promise((resolve) => {
|
|
1590
|
-
const candidates = findExplorerTabs(label);
|
|
1591
|
-
if (candidates.length === 0) { resolve(false); return; }
|
|
1592
|
-
let index = 0;
|
|
1593
|
-
const tryNext = () => {
|
|
1594
|
-
if (index >= candidates.length) { resolve(false); return; }
|
|
1595
|
-
const tab = candidates[index++];
|
|
1596
|
-
tab.click();
|
|
1597
|
-
const deadline = Date.now() + 400;
|
|
1598
|
-
(function check() {
|
|
1599
|
-
const rendered = document.querySelector(".mneme-x") !== null;
|
|
1600
|
-
if (tab.getAttribute("aria-selected") === "true" && rendered) { resolve(true); return; }
|
|
1601
|
-
if (Date.now() >= deadline) { setTimeout(tryNext, 0); return; }
|
|
1602
|
-
requestAnimationFrame(check);
|
|
1603
|
-
})();
|
|
1604
|
-
};
|
|
1605
|
-
tryNext();
|
|
1606
|
-
});
|
|
2103
|
+
// --- 打开记忆库 ---
|
|
2104
|
+
// v0.7.15 起记忆库不再注册 conversation.view tab:对话内嵌体验差
|
|
2105
|
+
// (面板被悬浮输入框遮挡、挤压会话布局)。唯一入口是侧边栏记忆按钮,
|
|
2106
|
+
// 直接打开居中 sheet——对话留在背板之后,不占全屏。保留这层间接函数
|
|
2107
|
+
// 是给图/实体视图的「查看来源记忆」跳转一个稳定语义:打开 sheet,
|
|
2108
|
+
// MemoryExplorer 内部状态(选中行、过滤)由各自的跳转回调处理。
|
|
2109
|
+
function openLibrary() {
|
|
2110
|
+
setOverlayOpen(true);
|
|
1607
2111
|
}
|
|
1608
2112
|
|
|
1609
2113
|
// --- Hero fallback overlay state (module-level pub/sub) ---
|
|
@@ -1624,11 +2128,9 @@ window.__ModuleLoader__.load({
|
|
|
1624
2128
|
return [open, setOverlayOpen];
|
|
1625
2129
|
}
|
|
1626
2130
|
|
|
1627
|
-
//
|
|
1628
|
-
//
|
|
1629
|
-
//
|
|
1630
|
-
// close affordance. Esc closes. Portalled to <body> so sidebar stacking
|
|
1631
|
-
// contexts cannot clip it.
|
|
2131
|
+
// 居中 sheet 记忆库:任何状态下(含新会话 hero、无 tab 环)都能从侧边栏
|
|
2132
|
+
// 入口直接打开。背板点击 / Esc 关闭。portal 到 <body>,侧边栏的层叠
|
|
2133
|
+
// 上下文裁不住它;运行时拒绝 react-dom 时就地渲染(position:fixed 仍然成立)。
|
|
1632
2134
|
function MemoryOverlay({ t }) {
|
|
1633
2135
|
const [open, setOpen] = useOverlayOpen();
|
|
1634
2136
|
useEffect(() => {
|
|
@@ -1638,16 +2140,30 @@ window.__ModuleLoader__.load({
|
|
|
1638
2140
|
return () => window.removeEventListener("keydown", onKey);
|
|
1639
2141
|
}, [open]);
|
|
1640
2142
|
if (!open) return null;
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
),
|
|
1650
|
-
|
|
2143
|
+
// 背板与 sheet 是兄弟节点而不是嵌套:点击背板自身才关闭,sheet 内部
|
|
2144
|
+
// 的任何点击都不会冒泡到背板,不需要额外拦截。
|
|
2145
|
+
const tree = h(react.Fragment, null,
|
|
2146
|
+
h("div", {
|
|
2147
|
+
className: "mneme-backdrop",
|
|
2148
|
+
onClick: () => setOpen(false),
|
|
2149
|
+
"aria-hidden": "true"
|
|
2150
|
+
}),
|
|
2151
|
+
h("div", { className: "mneme-overlay", role: "region", "aria-label": t("memory.view.label") },
|
|
2152
|
+
h("div", { className: "mneme-overlaybar" },
|
|
2153
|
+
h("span", { className: "mneme-overlaytitle" },
|
|
2154
|
+
h(IconArchiveOutline20, { size: 15 }),
|
|
2155
|
+
t("memory.view.label")
|
|
2156
|
+
),
|
|
2157
|
+
h("button", {
|
|
2158
|
+
type: "button",
|
|
2159
|
+
className: "mneme-footbtn",
|
|
2160
|
+
"aria-label": t("memory.overlay.close"),
|
|
2161
|
+
title: t("memory.overlay.close"),
|
|
2162
|
+
onClick: () => setOpen(false)
|
|
2163
|
+
}, "✕")
|
|
2164
|
+
),
|
|
2165
|
+
h("div", { className: "mneme-overlaybody" }, h(MemoryExplorer, { t }))
|
|
2166
|
+
)
|
|
1651
2167
|
);
|
|
1652
2168
|
if (reactDom && typeof document !== "undefined") return reactDom.createPortal(tree, document.body);
|
|
1653
2169
|
return tree;
|
|
@@ -1789,17 +2305,396 @@ window.__ModuleLoader__.load({
|
|
|
1789
2305
|
});
|
|
1790
2306
|
}
|
|
1791
2307
|
|
|
2308
|
+
// 巩固状态卡(×2):最近一次 autoDream 运行 + 待确认冲突计数。数据来自
|
|
2309
|
+
// /dream-status,一次取回两张卡共用;失败只影响这两张卡自身。
|
|
2310
|
+
function DreamStatusCards({ t }) {
|
|
2311
|
+
const [state, setState] = useState({ loading: true, error: false, lastRun: null, pending: 0 });
|
|
2312
|
+
useEffect(() => {
|
|
2313
|
+
let cancelled = false;
|
|
2314
|
+
apiFetch("/api/dsh-mneme/dream-status")
|
|
2315
|
+
.then((res) => { if (!res.ok) throw new Error("http"); return res.json(); })
|
|
2316
|
+
.then((d) => {
|
|
2317
|
+
if (cancelled) return;
|
|
2318
|
+
setState({
|
|
2319
|
+
loading: false,
|
|
2320
|
+
error: false,
|
|
2321
|
+
lastRun: (d && d.lastRun) || null,
|
|
2322
|
+
pending: Number((d && d.pendingConflicts) ?? 0)
|
|
2323
|
+
});
|
|
2324
|
+
})
|
|
2325
|
+
.catch(() => { if (!cancelled) setState({ loading: false, error: true, lastRun: null, pending: 0 }); });
|
|
2326
|
+
return () => { cancelled = true; };
|
|
2327
|
+
}, []);
|
|
2328
|
+
const run = state.lastRun;
|
|
2329
|
+
const num = run ? formatRelativeTime(run.created_at, t) : t("memory.status.dreamNever");
|
|
2330
|
+
const cap = run
|
|
2331
|
+
? `${run.status || "—"}${run.model ? ` · ${run.model}` : ""}`
|
|
2332
|
+
: t("memory.settings.mode.offList");
|
|
2333
|
+
return h(react.Fragment, null,
|
|
2334
|
+
h(StatusCard, { t, title: t("memory.status.dream"), loading: state.loading, error: state.error, num, cap }),
|
|
2335
|
+
h(StatusCard, {
|
|
2336
|
+
t,
|
|
2337
|
+
title: t("memory.status.conflicts"),
|
|
2338
|
+
loading: state.loading,
|
|
2339
|
+
error: state.error,
|
|
2340
|
+
num: state.pending.toLocaleString(),
|
|
2341
|
+
cap: t("memory.status.conflictsHint")
|
|
2342
|
+
})
|
|
2343
|
+
);
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
// --- 状态页工作台:让用户切实看见插件在干活 ---
|
|
2347
|
+
// 活动流合并 llm_audit 里 autoDream/autoSummarize 的后台调用(状态、
|
|
2348
|
+
// token、沉淀条数);沉淀记忆列表用 audit 行携带的 related_memory_ids
|
|
2349
|
+
// 对照最近列表解析出标题;归档列表走 /list?archived=only,「恢复」经
|
|
2350
|
+
// POST /update {archived:false} 送回主列表。
|
|
2351
|
+
function parseRelatedIds(v) {
|
|
2352
|
+
if (Array.isArray(v)) return v;
|
|
2353
|
+
if (typeof v === "string") {
|
|
2354
|
+
try { const a = JSON.parse(v); return Array.isArray(a) ? a : []; } catch { return []; }
|
|
2355
|
+
}
|
|
2356
|
+
return [];
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
function WorkbenchSection({ t }) {
|
|
2360
|
+
const [feed, setFeed] = useState(null); // audit rows(已过滤巩固/总结)
|
|
2361
|
+
const [deposited, setDeposited] = useState(null); // ids 解析出的记忆
|
|
2362
|
+
const [archived, setArchived] = useState(null);
|
|
2363
|
+
const [restoring, setRestoring] = useState("");
|
|
2364
|
+
|
|
2365
|
+
const load = useCallback(() => {
|
|
2366
|
+
let cancelled = false;
|
|
2367
|
+
apiFetch("/api/dsh-mneme/semantic/llm-audit?pageSize=12")
|
|
2368
|
+
.then((r) => (r.ok ? r.json() : { items: [] }))
|
|
2369
|
+
.then((d) => {
|
|
2370
|
+
if (cancelled) return;
|
|
2371
|
+
const rows = (d.items || []).filter((r) =>
|
|
2372
|
+
r.trigger_source === "autoDream" || r.trigger_source === "autoSummarize");
|
|
2373
|
+
setFeed(rows);
|
|
2374
|
+
const ids = [];
|
|
2375
|
+
for (const row of rows) {
|
|
2376
|
+
for (const id of parseRelatedIds(row.related_memory_ids)) {
|
|
2377
|
+
if (!ids.includes(id)) ids.push(id);
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
return apiFetch("/api/dsh-mneme/list?limit=300&order=chrono")
|
|
2381
|
+
.then((r2) => (r2.ok ? r2.json() : { items: [] }))
|
|
2382
|
+
.then((d2) => {
|
|
2383
|
+
if (cancelled) return;
|
|
2384
|
+
const byId = new Map((d2.items || []).map((m) => [m.id, m]));
|
|
2385
|
+
setDeposited(ids.map((id) => byId.get(id)).filter(Boolean).slice(0, 8));
|
|
2386
|
+
});
|
|
2387
|
+
})
|
|
2388
|
+
.catch(() => { if (!cancelled) setFeed([]); });
|
|
2389
|
+
apiFetch("/api/dsh-mneme/list?archived=only&limit=50&order=chrono")
|
|
2390
|
+
.then((r) => (r.ok ? r.json() : { items: [] }))
|
|
2391
|
+
.then((d) => { if (!cancelled) setArchived(d.items || []); })
|
|
2392
|
+
.catch(() => { if (!cancelled) setArchived([]); });
|
|
2393
|
+
return () => { cancelled = true; };
|
|
2394
|
+
}, []);
|
|
2395
|
+
useEffect(() => { load(); }, [load]);
|
|
2396
|
+
|
|
2397
|
+
const restore = (id) => {
|
|
2398
|
+
setRestoring(id);
|
|
2399
|
+
apiFetch("/api/dsh-mneme/update", {
|
|
2400
|
+
method: "POST",
|
|
2401
|
+
headers: { "Content-Type": "application/json" },
|
|
2402
|
+
body: JSON.stringify({ id, archived: false })
|
|
2403
|
+
})
|
|
2404
|
+
.then((r) => { if (!r.ok) throw new Error("http"); })
|
|
2405
|
+
.then(() => setArchived((cur) => (cur || []).filter((m) => m.id !== id)))
|
|
2406
|
+
.catch(() => {})
|
|
2407
|
+
.finally(() => setRestoring(""));
|
|
2408
|
+
};
|
|
2409
|
+
|
|
2410
|
+
const statusLabel = (s) => s === "error"
|
|
2411
|
+
? t("memory.status.failed")
|
|
2412
|
+
: s === "skipped" ? t("memory.status.skipped") : t("memory.status.success");
|
|
2413
|
+
const triggerLabel = (s) => s === "autoDream"
|
|
2414
|
+
? t("memory.status.dreamConsolidate") : t("memory.status.summarize");
|
|
2415
|
+
|
|
2416
|
+
return h(react.Fragment, null,
|
|
2417
|
+
h("div", { className: "mneme-wbhead" }, t("memory.status.workbench")),
|
|
2418
|
+
feed === null
|
|
2419
|
+
? h("div", { className: "mneme-featsubhint" }, "…")
|
|
2420
|
+
: feed.length === 0
|
|
2421
|
+
? h("div", { className: "mneme-featsubhint" }, t("memory.status.emptyFeed"))
|
|
2422
|
+
: h("div", { className: "mneme-wblist" },
|
|
2423
|
+
feed.map((row) => {
|
|
2424
|
+
const n = parseRelatedIds(row.related_memory_ids).length;
|
|
2425
|
+
return h("div", { className: "mneme-wbrow", key: row.id ?? row.timestamp },
|
|
2426
|
+
h("span", { className: row.status === "error" ? "mneme-wbdot mneme-wbdot--err" : "mneme-wbdot", "aria-hidden": "true" }),
|
|
2427
|
+
h("div", { className: "mneme-wbmain" },
|
|
2428
|
+
h("div", { className: "mneme-wbtitle" },
|
|
2429
|
+
`${triggerLabel(row.trigger_source)} · ${statusLabel(row.status)}`),
|
|
2430
|
+
h("div", { className: "mneme-wbsub" },
|
|
2431
|
+
formatRelativeTime(row.timestamp, t),
|
|
2432
|
+
n > 0 && ` · ${t("memory.status.deposited").replace("{n}", String(n))}`,
|
|
2433
|
+
row.total_tokens ? ` · ${t("memory.status.tokens").replace("{n}", Number(row.total_tokens).toLocaleString())}` : "",
|
|
2434
|
+
row.error_message ? ` · ${row.error_message}` : "")
|
|
2435
|
+
));
|
|
2436
|
+
})),
|
|
2437
|
+
deposited !== null && deposited.length > 0 && h(react.Fragment, null,
|
|
2438
|
+
h("div", { className: "mneme-wbhead" }, t("memory.status.consolidated")),
|
|
2439
|
+
h("div", { className: "mneme-wblist" },
|
|
2440
|
+
deposited.map((m) => h("div", { className: "mneme-wbmemo", key: m.id },
|
|
2441
|
+
h("span", { className: "mneme-xdot", style: { color: memoryTypeColor(m.type) }, "aria-hidden": "true" }),
|
|
2442
|
+
h("div", { className: "mneme-wbmemo-main" },
|
|
2443
|
+
h("div", { className: "mneme-wbtitle" }, m.title || (m.content || "").slice(0, 40)),
|
|
2444
|
+
h("div", { className: "mneme-wbsub" },
|
|
2445
|
+
`${typeLabel(t, m.type)} · ${formatRelativeTime(m.updated_at || m.created_at, t)}`))
|
|
2446
|
+
)))),
|
|
2447
|
+
archived !== null && h(react.Fragment, null,
|
|
2448
|
+
h("div", { className: "mneme-wbhead" }, t("memory.status.archivedMemories")),
|
|
2449
|
+
archived.length === 0
|
|
2450
|
+
? h("div", { className: "mneme-featsubhint" }, t("memory.status.archivedEmpty"))
|
|
2451
|
+
: h("div", { className: "mneme-wblist" },
|
|
2452
|
+
archived.map((m) => h("div", { className: "mneme-wbmemo", key: m.id },
|
|
2453
|
+
h("span", { className: "mneme-xdot", style: { color: memoryTypeColor(m.type) }, "aria-hidden": "true" }),
|
|
2454
|
+
h("div", { className: "mneme-wbmemo-main" },
|
|
2455
|
+
h("div", { className: "mneme-wbtitle" }, m.title || (m.content || "").slice(0, 40)),
|
|
2456
|
+
h("div", { className: "mneme-wbsub" },
|
|
2457
|
+
`${typeLabel(t, m.type)} · ${formatRelativeTime(m.updated_at || m.created_at, t)}`)),
|
|
2458
|
+
h("button", {
|
|
2459
|
+
type: "button",
|
|
2460
|
+
className: "mneme-btn",
|
|
2461
|
+
disabled: restoring === m.id,
|
|
2462
|
+
onClick: () => restore(m.id)
|
|
2463
|
+
}, t("memory.status.restore"))
|
|
2464
|
+
))))
|
|
2465
|
+
);
|
|
2466
|
+
}
|
|
2467
|
+
|
|
1792
2468
|
function StatusPanel({ t }) {
|
|
1793
2469
|
return h("div", { className: "mneme-status" },
|
|
1794
2470
|
h("div", { className: "mneme-statusgrid" },
|
|
1795
2471
|
h(MemoriesStatusCard, { t }),
|
|
1796
2472
|
h(EntitiesStatusCard, { t }),
|
|
1797
2473
|
h(VectorStatusCard, { t }),
|
|
1798
|
-
h(LlmStatusCard, { t })
|
|
2474
|
+
h(LlmStatusCard, { t }),
|
|
2475
|
+
h(DreamStatusCards, { t })
|
|
2476
|
+
),
|
|
2477
|
+
h(WorkbenchSection, { t })
|
|
2478
|
+
);
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
|
|
2482
|
+
// --- 详情抽屉 ---
|
|
2483
|
+
// 右侧滑出:全文、来源、质量分、关联实体与手动操作(编辑/归档/删除)。
|
|
2484
|
+
// 编辑态本地暂存草稿,保存经 POST /update 落库;后端成功后重渲染镜像,
|
|
2485
|
+
// 前端只做本地视图同步。两步删除确认在此完成(红钮武装 → 实心红提交)。
|
|
2486
|
+
function MemoryDrawer({ t, memory, conflict, deleting, deleteError, onClose, onDelete, onSaved }) {
|
|
2487
|
+
const [editing, setEditing] = useState(false);
|
|
2488
|
+
const [title, setTitle] = useState(memory.title || "");
|
|
2489
|
+
const [content, setContent] = useState(memory.content || "");
|
|
2490
|
+
const [importance, setImportance] = useState(memory.importance || 3);
|
|
2491
|
+
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
2492
|
+
const [busy, setBusy] = useState(false);
|
|
2493
|
+
const [savedTick, setSavedTick] = useState(false);
|
|
2494
|
+
const [saveError, setSaveError] = useState(false);
|
|
2495
|
+
const [copied, setCopied] = useState(false);
|
|
2496
|
+
const [entities, setEntities] = useState(null);
|
|
2497
|
+
|
|
2498
|
+
useEffect(() => {
|
|
2499
|
+
let cancelled = false;
|
|
2500
|
+
apiFetch(`/api/dsh-mneme/memories/entities?memoryId=${encodeURIComponent(memory.id)}`)
|
|
2501
|
+
.then((res) => (res.ok ? res.json() : { entities: [] }))
|
|
2502
|
+
.then((d) => { if (!cancelled) setEntities(d.entities || []); })
|
|
2503
|
+
.catch(() => { if (!cancelled) setEntities([]); });
|
|
2504
|
+
return () => { cancelled = true; };
|
|
2505
|
+
}, [memory.id]);
|
|
2506
|
+
|
|
2507
|
+
const postUpdate = (patch, opts) => {
|
|
2508
|
+
setBusy(true);
|
|
2509
|
+
setSaveError(false);
|
|
2510
|
+
apiFetch("/api/dsh-mneme/update", {
|
|
2511
|
+
method: "POST",
|
|
2512
|
+
headers: { "Content-Type": "application/json" },
|
|
2513
|
+
body: JSON.stringify({ id: memory.id, ...patch })
|
|
2514
|
+
})
|
|
2515
|
+
.then((res) => { if (!res.ok) throw new Error("http"); return res.json(); })
|
|
2516
|
+
.then((d) => {
|
|
2517
|
+
onSaved(d.memory, opts);
|
|
2518
|
+
setEditing(false);
|
|
2519
|
+
setSavedTick(true);
|
|
2520
|
+
setTimeout(() => setSavedTick(false), 2000);
|
|
2521
|
+
})
|
|
2522
|
+
.catch(() => setSaveError(true))
|
|
2523
|
+
.finally(() => setBusy(false));
|
|
2524
|
+
};
|
|
2525
|
+
|
|
2526
|
+
const save = () => {
|
|
2527
|
+
const t2 = title.trim();
|
|
2528
|
+
if (!t2) return;
|
|
2529
|
+
postUpdate({ title: t2, content, importance }, {});
|
|
2530
|
+
};
|
|
2531
|
+
|
|
2532
|
+
return h("aside", { className: "mneme-drawer", "aria-label": t("memory.explorer.detail") },
|
|
2533
|
+
h("div", { className: "mneme-drawerbar" },
|
|
2534
|
+
h("span", { className: "mneme-drawertype" },
|
|
2535
|
+
h("span", { className: "mneme-xdot", style: { color: memoryTypeColor(memory.type) }, "aria-hidden": "true" }),
|
|
2536
|
+
typeLabel(t, memory.type)
|
|
2537
|
+
),
|
|
2538
|
+
conflict && h("span", { className: "mneme-badge mneme-badge--conflict" }, "⚠ ", t("memory.explorer.conflictBadge")),
|
|
2539
|
+
h("button", {
|
|
2540
|
+
type: "button",
|
|
2541
|
+
className: "mneme-footbtn",
|
|
2542
|
+
style: { marginLeft: "auto" },
|
|
2543
|
+
"aria-label": t("memory.explorer.detail.closeAria"),
|
|
2544
|
+
title: t("memory.explorer.detail.closeAria"),
|
|
2545
|
+
onClick: onClose
|
|
2546
|
+
}, "✕")
|
|
2547
|
+
),
|
|
2548
|
+
h("div", { className: "mneme-drawerbody" },
|
|
2549
|
+
editing
|
|
2550
|
+
? h(react.Fragment, null,
|
|
2551
|
+
h("div", { className: "mneme-xcolhead", style: { padding: "0 0 4px" } }, t("memory.explorer.detail.editTitle")),
|
|
2552
|
+
h("input", { className: "mneme-drawertitle-input", value: title, onChange: (e) => setTitle(e.target.value) }))
|
|
2553
|
+
: h("div", { className: "mneme-drawertitle" }, memory.title || t("memory.panel.empty")),
|
|
2554
|
+
h("div", { className: "mneme-dmeta" },
|
|
2555
|
+
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.importance")),
|
|
2556
|
+
editing
|
|
2557
|
+
? h("select", {
|
|
2558
|
+
className: "mneme-select",
|
|
2559
|
+
style: { height: 26, fontSize: 12, justifySelf: "start" },
|
|
2560
|
+
value: importance,
|
|
2561
|
+
onChange: (e) => setImportance(Number(e.target.value))
|
|
2562
|
+
}, [1, 2, 3, 4, 5].map((n) => h("option", { key: n, value: n }, "★".repeat(n))))
|
|
2563
|
+
: h("span", { className: "mneme-dmetaval" }, "★".repeat(Math.min(5, Math.max(0, memory.importance || 0)))),
|
|
2564
|
+
memory.source && h(react.Fragment, null,
|
|
2565
|
+
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.source")),
|
|
2566
|
+
h("span", { className: "mneme-dmetaval", title: memory.source }, memory.source)),
|
|
2567
|
+
memory.quality_score != null && h(react.Fragment, null,
|
|
2568
|
+
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.detail.quality")),
|
|
2569
|
+
h("span", { className: "mneme-dmetaval" }, String(memory.quality_score))),
|
|
2570
|
+
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.created")),
|
|
2571
|
+
h("span", { className: "mneme-dmetaval", title: formatDate(memory.created_at) }, formatDateShort(memory.created_at)),
|
|
2572
|
+
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.updated")),
|
|
2573
|
+
h("span", { className: "mneme-dmetaval", title: formatDate(memory.updated_at) },
|
|
2574
|
+
`${formatRelativeTime(memory.updated_at, t)}(${formatDateShort(memory.updated_at)})`),
|
|
2575
|
+
Array.isArray(memory.tags) && memory.tags.length > 0 && h(react.Fragment, null,
|
|
2576
|
+
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.tags")),
|
|
2577
|
+
h("span", { className: "mneme-dmetaval" }, memory.tags.join(" · ")))
|
|
2578
|
+
),
|
|
2579
|
+
entities !== null && h(react.Fragment, null,
|
|
2580
|
+
h("div", { className: "mneme-xcolhead", style: { padding: "16px 0 0" } }, t("memory.explorer.detail.entities")),
|
|
2581
|
+
entities.length === 0
|
|
2582
|
+
? h("div", { className: "mneme-featsubhint", style: { marginTop: 4 } }, t("memory.explorer.detail.entitiesEmpty"))
|
|
2583
|
+
: h("div", { className: "mneme-dentities" },
|
|
2584
|
+
entities.map((e, i) => h("span", { key: `${e.name}-${i}`, className: "mnementity-chip" },
|
|
2585
|
+
h("span", { className: "mneme-xdot", style: { color: typeColor(e.type) }, "aria-hidden": "true" }),
|
|
2586
|
+
e.name
|
|
2587
|
+
)))),
|
|
2588
|
+
editing && h("div", { className: "mneme-xcolhead", style: { padding: "16px 0 4px" } }, t("memory.explorer.detail.editContent")),
|
|
2589
|
+
editing
|
|
2590
|
+
? h("textarea", { className: "mneme-dcontent-input", value: content, onChange: (e) => setContent(e.target.value) })
|
|
2591
|
+
: h("div", { className: "mneme-dcontent" }, memory.content)
|
|
2592
|
+
),
|
|
2593
|
+
h("div", { className: "mneme-dactions" },
|
|
2594
|
+
editing
|
|
2595
|
+
? h(react.Fragment, null,
|
|
2596
|
+
h("button", { type: "button", className: "mneme-btn", disabled: busy, onClick: save }, t("memory.explorer.detail.save")),
|
|
2597
|
+
h("button", {
|
|
2598
|
+
type: "button",
|
|
2599
|
+
className: "mneme-btn",
|
|
2600
|
+
disabled: busy,
|
|
2601
|
+
onClick: () => {
|
|
2602
|
+
setEditing(false);
|
|
2603
|
+
setTitle(memory.title || "");
|
|
2604
|
+
setContent(memory.content || "");
|
|
2605
|
+
setImportance(memory.importance || 3);
|
|
2606
|
+
}
|
|
2607
|
+
}, t("memory.explorer.detail.cancel")),
|
|
2608
|
+
savedTick && h("span", { className: "mneme-saved" }, t("memory.explorer.detail.saved")),
|
|
2609
|
+
saveError && h("span", { style: { fontSize: 12, color: "var(--dsw-alias-state-error,#c33)" } }, t("memory.explorer.detail.archiveFailed"))
|
|
2610
|
+
)
|
|
2611
|
+
: h(react.Fragment, null,
|
|
2612
|
+
h("button", { type: "button", className: "mneme-btn", onClick: () => setEditing(true) }, t("memory.explorer.detail.edit")),
|
|
2613
|
+
h("button", { type: "button", className: "mneme-btn", disabled: busy, onClick: () => postUpdate({ archived: true }, { archived: true }) },
|
|
2614
|
+
t("memory.explorer.detail.archive")),
|
|
2615
|
+
h("button", {
|
|
2616
|
+
type: "button",
|
|
2617
|
+
className: "mneme-btn",
|
|
2618
|
+
onClick: () => {
|
|
2619
|
+
navigator.clipboard?.writeText(memory.content || "").then(
|
|
2620
|
+
() => { setCopied(true); setTimeout(() => setCopied(false), 1500); },
|
|
2621
|
+
() => {}
|
|
2622
|
+
);
|
|
2623
|
+
}
|
|
2624
|
+
}, copied ? t("memory.explorer.copied") : t("memory.explorer.copy")),
|
|
2625
|
+
confirmDelete
|
|
2626
|
+
? h(react.Fragment, null,
|
|
2627
|
+
h("button", { type: "button", className: "mneme-btn mneme-btndangerconfirm", disabled: deleting, onClick: () => onDelete(memory.id) },
|
|
2628
|
+
t("memory.explorer.confirmDelete")),
|
|
2629
|
+
h("button", { type: "button", className: "mneme-btn", onClick: () => setConfirmDelete(false) },
|
|
2630
|
+
t("memory.explorer.cancel"))
|
|
2631
|
+
)
|
|
2632
|
+
: h("button", { type: "button", className: "mneme-btn mneme-btndanger", onClick: () => setConfirmDelete(true) },
|
|
2633
|
+
t("memory.explorer.delete")),
|
|
2634
|
+
deleteError && h("span", { style: { fontSize: 12, color: "var(--dsw-alias-state-error,#c33)" } },
|
|
2635
|
+
t("memory.explorer.deleteFailed"))
|
|
2636
|
+
)
|
|
1799
2637
|
)
|
|
1800
2638
|
);
|
|
1801
2639
|
}
|
|
1802
2640
|
|
|
2641
|
+
// --- 导入弹层 ---
|
|
2642
|
+
// 选类型 → 选文件 → 解析合并。文件在本地读成文本,POST /import 只传
|
|
2643
|
+
// 文本与类型;成功后提示合并条数,并由父级刷新列表。
|
|
2644
|
+
function ImportDialog({ t, onClose, onImported }) {
|
|
2645
|
+
const [type, setType] = useState("preference");
|
|
2646
|
+
const [file, setFile] = useState(null);
|
|
2647
|
+
const [busy, setBusy] = useState(false);
|
|
2648
|
+
const [msg, setMsg] = useState("");
|
|
2649
|
+
const [err, setErr] = useState("");
|
|
2650
|
+
const fileRef = useRef(null);
|
|
2651
|
+
const submit = () => {
|
|
2652
|
+
if (!file) { setErr(t("memory.explorer.importNoFile")); return; }
|
|
2653
|
+
setBusy(true);
|
|
2654
|
+
setErr("");
|
|
2655
|
+
file.text()
|
|
2656
|
+
.then((text) => apiFetch("/api/dsh-mneme/import", {
|
|
2657
|
+
method: "POST",
|
|
2658
|
+
headers: { "Content-Type": "application/json" },
|
|
2659
|
+
body: JSON.stringify({ type, markdown: text })
|
|
2660
|
+
}))
|
|
2661
|
+
.then((res) => { if (!res.ok) throw new Error("http"); return res.json(); })
|
|
2662
|
+
.then((d) => {
|
|
2663
|
+
setMsg(t("memory.explorer.imported").replace("{n}", String((d && d.merged) ?? 0)));
|
|
2664
|
+
setBusy(false);
|
|
2665
|
+
setTimeout(() => { onImported(); onClose(); }, 900);
|
|
2666
|
+
})
|
|
2667
|
+
.catch(() => { setBusy(false); setErr(t("memory.explorer.importFailed")); });
|
|
2668
|
+
};
|
|
2669
|
+
return h("div", { className: "mneme-impdialog", role: "region", "aria-label": t("memory.explorer.importTitle") },
|
|
2670
|
+
h("div", { className: "mneme-set-title" }, t("memory.explorer.importTitle")),
|
|
2671
|
+
h("div", { className: "mneme-featsubhint", style: { marginTop: 4 } }, t("memory.explorer.importHint")),
|
|
2672
|
+
h("div", { className: "mneme-improw" },
|
|
2673
|
+
h("span", { className: "mneme-featnumlabel" }, t("memory.explorer.importType")),
|
|
2674
|
+
h("select", { className: "mneme-select", value: type, onChange: (e) => setType(e.target.value) },
|
|
2675
|
+
["preference", "project", "decision", "summary", "history"].map((k) =>
|
|
2676
|
+
h("option", { key: k, value: k }, typeLabel(t, k))))
|
|
2677
|
+
),
|
|
2678
|
+
h("div", { className: "mneme-improw" },
|
|
2679
|
+
h("button", { type: "button", className: "mneme-btn", onClick: () => fileRef.current?.click() },
|
|
2680
|
+
file ? file.name : t("memory.explorer.importPick")),
|
|
2681
|
+
h("input", {
|
|
2682
|
+
ref: fileRef,
|
|
2683
|
+
type: "file",
|
|
2684
|
+
accept: ".md,.markdown,text/markdown",
|
|
2685
|
+
style: { display: "none" },
|
|
2686
|
+
onChange: (e) => { setFile((e.target.files && e.target.files[0]) || null); setErr(""); }
|
|
2687
|
+
})
|
|
2688
|
+
),
|
|
2689
|
+
err && h("div", { className: "mneme-featsubhint", style: { color: "var(--dsw-alias-state-error,#c33)", marginTop: 8 } }, err),
|
|
2690
|
+
msg && h("div", { className: "mneme-saved", style: { marginTop: 8 } }, msg),
|
|
2691
|
+
h("div", { className: "mneme-improw", style: { justifyContent: "flex-end" } },
|
|
2692
|
+
h("button", { type: "button", className: "mneme-btn", disabled: busy, onClick: onClose }, t("memory.explorer.importCancel")),
|
|
2693
|
+
h("button", { type: "button", className: "mneme-btn", disabled: busy, onClick: submit },
|
|
2694
|
+
busy ? t("memory.explorer.importing") : t("memory.explorer.importConfirm"))
|
|
2695
|
+
)
|
|
2696
|
+
);
|
|
2697
|
+
}
|
|
1803
2698
|
|
|
1804
2699
|
function MemoryExplorer({ t }) {
|
|
1805
2700
|
const [view, setView] = useState("memory"); // memory | entity | status | settings
|
|
@@ -1817,16 +2712,29 @@ window.__ModuleLoader__.load({
|
|
|
1817
2712
|
const [selectedId, setSelectedId] = useState(null);
|
|
1818
2713
|
const [expandedMonths, setExpandedMonths] = useState(null); // null = 仅最新一个月展开
|
|
1819
2714
|
const [collapsed, setCollapsed] = useState({});
|
|
1820
|
-
const [copied, setCopied] = useState(false);
|
|
1821
2715
|
const [reloadKey, setReloadKey] = useState(0);
|
|
1822
|
-
const [confirmDelete, setConfirmDelete] = useState(false); // two-step delete armed
|
|
1823
2716
|
const [deleting, setDeleting] = useState(false);
|
|
1824
2717
|
const [deleteError, setDeleteError] = useState(false);
|
|
1825
|
-
const [
|
|
2718
|
+
const [toastMsg, setToastMsg] = useState(""); // 瞬时操作提示(删除/归档),挂在外层不被抽屉卸载吞掉
|
|
1826
2719
|
const [graphFocus, setGraphFocus] = useState("");
|
|
2720
|
+
// 交互式面板新增:浏览模式(卡片/时间线,本地记住)、时间筛选、
|
|
2721
|
+
// 冲突冻结指示、更多操作菜单与导入弹层。
|
|
2722
|
+
const [viewMode, setViewMode] = useState(() => {
|
|
2723
|
+
try { return window.localStorage.getItem("dsh-mneme-view") === "cards" ? "cards" : "timeline"; }
|
|
2724
|
+
catch { return "timeline"; }
|
|
2725
|
+
});
|
|
2726
|
+
const [dateRange, setDateRange] = useState("all"); // all | 7d | 30d | 90d
|
|
2727
|
+
const [pendingIds, setPendingIds] = useState(() => new Set());
|
|
2728
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
2729
|
+
const [importOpen, setImportOpen] = useState(false);
|
|
1827
2730
|
const itemRefs = useRef(new Map());
|
|
1828
2731
|
const moreRef = useRef(null);
|
|
1829
2732
|
|
|
2733
|
+
const switchViewMode = (mode) => {
|
|
2734
|
+
setViewMode(mode);
|
|
2735
|
+
try { window.localStorage.setItem("dsh-mneme-view", mode); } catch { /* 隐私模式等 */ }
|
|
2736
|
+
};
|
|
2737
|
+
|
|
1830
2738
|
useEffect(() => {
|
|
1831
2739
|
apiFetch("/api/dsh-mneme/vector-config")
|
|
1832
2740
|
.then((res) => res.json())
|
|
@@ -1834,11 +2742,27 @@ window.__ModuleLoader__.load({
|
|
|
1834
2742
|
.catch(() => {});
|
|
1835
2743
|
}, []);
|
|
1836
2744
|
|
|
2745
|
+
// 冲突冻结指示:dream-status 一次带出待确认冲突涉及的记忆 id,
|
|
2746
|
+
// 卡片与详情抽屉据此画「冲突」徽章。
|
|
2747
|
+
useEffect(() => {
|
|
2748
|
+
let cancelled = false;
|
|
2749
|
+
apiFetch("/api/dsh-mneme/dream-status")
|
|
2750
|
+
.then((res) => (res.ok ? res.json() : { pendingMemoryIds: [] }))
|
|
2751
|
+
.then((d) => { if (!cancelled) setPendingIds(new Set(d.pendingMemoryIds || [])); })
|
|
2752
|
+
.catch(() => {});
|
|
2753
|
+
return () => { cancelled = true; };
|
|
2754
|
+
}, [reloadKey]);
|
|
2755
|
+
|
|
1837
2756
|
// One query string behind every browse-list fetch (initial page,
|
|
1838
|
-
// loadMore, auto-refresh): the type filter
|
|
1839
|
-
// always travel together, so counts and pagination stay
|
|
2757
|
+
// loadMore, auto-refresh): the type filter, importance floor and time
|
|
2758
|
+
// window always travel together, so counts and pagination stay
|
|
2759
|
+
// consistent.
|
|
2760
|
+
const dateFromIso = dateRange === "all" ? "" : new Date(
|
|
2761
|
+
Date.now() - (dateRange === "7d" ? 7 : dateRange === "30d" ? 30 : 90) * 86400000
|
|
2762
|
+
).toISOString();
|
|
1840
2763
|
const filterQS = (type === "all" ? "" : `&type=${encodeURIComponent(type)}`)
|
|
1841
|
-
+ (minImp ? `&minImportance=${minImp}` : "")
|
|
2764
|
+
+ (minImp ? `&minImportance=${minImp}` : "")
|
|
2765
|
+
+ (dateFromIso ? `&updatedFrom=${encodeURIComponent(dateFromIso)}` : "");
|
|
1842
2766
|
const listUrl = (offset) => `/api/dsh-mneme/list?limit=${PAGE_SIZE}&offset=${offset}${filterQS}&order=chrono`;
|
|
1843
2767
|
|
|
1844
2768
|
// Browse = paged, pure-chronological pages (order=chrono). The default
|
|
@@ -1935,10 +2859,10 @@ window.__ModuleLoader__.load({
|
|
|
1935
2859
|
itemRefs.current.get(selectedId)?.scrollIntoView({ block: "nearest" });
|
|
1936
2860
|
}, [selectedId]);
|
|
1937
2861
|
|
|
1938
|
-
// The two-step delete arms per selection: switching
|
|
1939
|
-
// target disarms it, so a stale confirm can never
|
|
2862
|
+
// The two-step delete arms inside the drawer per selection: switching
|
|
2863
|
+
// or clearing the target disarms it, so a stale confirm can never
|
|
2864
|
+
// delete a new pick.
|
|
1940
2865
|
useEffect(() => {
|
|
1941
|
-
setConfirmDelete(false);
|
|
1942
2866
|
setDeleteError(false);
|
|
1943
2867
|
}, [selectedId]);
|
|
1944
2868
|
|
|
@@ -1993,22 +2917,12 @@ window.__ModuleLoader__.load({
|
|
|
1993
2917
|
|
|
1994
2918
|
const selected = visible.find((m) => m.id === selectedId) || null;
|
|
1995
2919
|
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
);
|
|
2002
|
-
};
|
|
2003
|
-
|
|
2004
|
-
// Two-step delete (no window.confirm): the red button arms, a second
|
|
2005
|
-
// click commits POST /api/dsh-mneme/delete. On success the row leaves
|
|
2006
|
-
// every local view (browse pages + search results), the selection
|
|
2007
|
-
// clears and the total shrinks; failures — 404 included — surface as
|
|
2008
|
-
// a transient red note next to the actions.
|
|
2009
|
-
const deleteSelected = () => {
|
|
2010
|
-
if (!selected || deleting) return;
|
|
2011
|
-
const id = selected.id;
|
|
2920
|
+
// Delete / archive / edit commit through POST endpoints; on success the
|
|
2921
|
+
// row leaves or updates in every local view (browse pages + search
|
|
2922
|
+
// results). Failures — 404 included — surface as a transient red note
|
|
2923
|
+
// in the drawer's action bar.
|
|
2924
|
+
const deleteById = (id) => {
|
|
2925
|
+
if (!id || deleting) return;
|
|
2012
2926
|
setDeleting(true);
|
|
2013
2927
|
setDeleteError(false);
|
|
2014
2928
|
apiFetch("/api/dsh-mneme/delete", {
|
|
@@ -2022,9 +2936,8 @@ window.__ModuleLoader__.load({
|
|
|
2022
2936
|
setRemoteItems((cur) => (cur ? cur.filter((m) => m.id !== id) : cur));
|
|
2023
2937
|
setTotal((n) => Math.max(0, n - 1));
|
|
2024
2938
|
setSelectedId(null);
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
setTimeout(() => setDeleteMsg(false), 2500);
|
|
2939
|
+
setToastMsg(t("memory.explorer.deleted"));
|
|
2940
|
+
setTimeout(() => setToastMsg(""), 2500);
|
|
2028
2941
|
})
|
|
2029
2942
|
.catch(() => {
|
|
2030
2943
|
setDeleteError(true);
|
|
@@ -2033,6 +2946,44 @@ window.__ModuleLoader__.load({
|
|
|
2033
2946
|
.finally(() => setDeleting(false));
|
|
2034
2947
|
};
|
|
2035
2948
|
|
|
2949
|
+
// 编辑保存 / 归档的本地落账:替换或移除对应行;镜像由后端重渲染。
|
|
2950
|
+
// 归档从所有视图移除(列表默认只看未归档),并清空选中。
|
|
2951
|
+
const applyMemoryUpdate = (updated, { archived = false } = {}) => {
|
|
2952
|
+
if (archived) {
|
|
2953
|
+
setItems((cur) => cur.filter((m) => m.id !== updated.id));
|
|
2954
|
+
setRemoteItems((cur) => (cur ? cur.filter((m) => m.id !== updated.id) : cur));
|
|
2955
|
+
setTotal((n) => Math.max(0, n - 1));
|
|
2956
|
+
setSelectedId(null);
|
|
2957
|
+
setToastMsg(t("memory.explorer.detail.archived"));
|
|
2958
|
+
setTimeout(() => setToastMsg(""), 2500);
|
|
2959
|
+
return;
|
|
2960
|
+
}
|
|
2961
|
+
const replace = (m) => (m.id === updated.id ? updated : m);
|
|
2962
|
+
setItems((cur) => cur.map(replace));
|
|
2963
|
+
setRemoteItems((cur) => (cur ? cur.map(replace) : cur));
|
|
2964
|
+
};
|
|
2965
|
+
|
|
2966
|
+
// 导出:JSON 走响应对象重序列化(缩进美化),Markdown 原样落盘;
|
|
2967
|
+
// 文件名带日期,浏览器下载不动主列表。
|
|
2968
|
+
const exportData = (format) => {
|
|
2969
|
+
apiFetch(`/api/dsh-mneme/export?format=${format}`)
|
|
2970
|
+
.then(async (res) => {
|
|
2971
|
+
if (!res.ok) throw new Error("http");
|
|
2972
|
+
const blob = format === "json"
|
|
2973
|
+
? new Blob([JSON.stringify(await res.json(), null, 2)], { type: "application/json" })
|
|
2974
|
+
: new Blob([await res.text()], { type: "text/markdown" });
|
|
2975
|
+
const url = URL.createObjectURL(blob);
|
|
2976
|
+
const a = document.createElement("a");
|
|
2977
|
+
a.href = url;
|
|
2978
|
+
a.download = `dsh-mneme-export-${new Date().toISOString().slice(0, 10)}.${format === "json" ? "json" : "md"}`;
|
|
2979
|
+
document.body.appendChild(a);
|
|
2980
|
+
a.click();
|
|
2981
|
+
a.remove();
|
|
2982
|
+
URL.revokeObjectURL(url);
|
|
2983
|
+
})
|
|
2984
|
+
.catch(() => {});
|
|
2985
|
+
};
|
|
2986
|
+
|
|
2036
2987
|
// Graph → memory jump: land on the browser tab with filters reset so
|
|
2037
2988
|
// the target row is visible, selected and scrolled into view. A target
|
|
2038
2989
|
// beyond the loaded pages renders as a single-row result set instead
|
|
@@ -2074,7 +3025,45 @@ window.__ModuleLoader__.load({
|
|
|
2074
3025
|
s.label
|
|
2075
3026
|
))
|
|
2076
3027
|
),
|
|
3028
|
+
view === "memory" && h("div", { className: "mneme-xtools" },
|
|
3029
|
+
h("div", { className: "mneme-seg", role: "group", "aria-label": t("memory.explorer.viewAria") },
|
|
3030
|
+
h("button", {
|
|
3031
|
+
type: "button",
|
|
3032
|
+
className: viewMode === "cards" ? "mneme-active" : "",
|
|
3033
|
+
onClick: () => switchViewMode("cards")
|
|
3034
|
+
}, t("memory.explorer.viewCards")),
|
|
3035
|
+
h("button", {
|
|
3036
|
+
type: "button",
|
|
3037
|
+
className: viewMode === "timeline" ? "mneme-active" : "",
|
|
3038
|
+
onClick: () => switchViewMode("timeline")
|
|
3039
|
+
}, t("memory.explorer.viewTimeline"))
|
|
3040
|
+
),
|
|
3041
|
+
h("div", { className: "mneme-menuwrap" },
|
|
3042
|
+
h("button", {
|
|
3043
|
+
type: "button",
|
|
3044
|
+
className: "mneme-footbtn",
|
|
3045
|
+
style: { fontSize: 15, padding: "3px 9px" },
|
|
3046
|
+
"aria-label": t("memory.explorer.more"),
|
|
3047
|
+
"aria-expanded": String(menuOpen),
|
|
3048
|
+
title: t("memory.explorer.more"),
|
|
3049
|
+
onClick: () => { setMenuOpen(!menuOpen); setImportOpen(false); }
|
|
3050
|
+
}, "⋯"),
|
|
3051
|
+
menuOpen && h("div", { className: "mneme-menu", role: "menu" },
|
|
3052
|
+
h("button", { type: "button", role: "menuitem", onClick: () => { setMenuOpen(false); exportData("json"); } },
|
|
3053
|
+
t("memory.explorer.exportJson")),
|
|
3054
|
+
h("button", { type: "button", role: "menuitem", onClick: () => { setMenuOpen(false); exportData("markdown"); } },
|
|
3055
|
+
t("memory.explorer.exportMarkdown")),
|
|
3056
|
+
h("button", { type: "button", role: "menuitem", onClick: () => { setImportOpen(true); setMenuOpen(false); } },
|
|
3057
|
+
t("memory.explorer.importMd"))
|
|
3058
|
+
),
|
|
3059
|
+
importOpen && h(ImportDialog, {
|
|
3060
|
+
t,
|
|
3061
|
+
onClose: () => setImportOpen(false),
|
|
3062
|
+
onImported: () => setReloadKey((k) => k + 1)
|
|
3063
|
+
})
|
|
3064
|
+
)
|
|
2077
3065
|
),
|
|
3066
|
+
),
|
|
2078
3067
|
view === "memory" && h("div", { className: "mneme-xmain" },
|
|
2079
3068
|
h("div", { className: "mneme-xside" },
|
|
2080
3069
|
h("div", { className: "mneme-xcolhead" }, t("memory.explorer.searchTitle")),
|
|
@@ -2105,6 +3094,17 @@ window.__ModuleLoader__.load({
|
|
|
2105
3094
|
title: t("memory.explorer.topK")
|
|
2106
3095
|
}, [5, 10, 20, 50].map((n) => h("option", { key: n, value: n }, t("memory.explorer.topKOption").replace("{n}", String(n)))))
|
|
2107
3096
|
),
|
|
3097
|
+
h("div", { className: "mneme-xrow" },
|
|
3098
|
+
h("select", {
|
|
3099
|
+
className: "mneme-select mneme-xselect",
|
|
3100
|
+
value: dateRange,
|
|
3101
|
+
onChange: (e) => setDateRange(e.target.value),
|
|
3102
|
+
title: t("memory.explorer.dateLabel"),
|
|
3103
|
+
"aria-label": t("memory.explorer.dateLabel")
|
|
3104
|
+
},
|
|
3105
|
+
["all", "7d", "30d", "90d"].map((k) =>
|
|
3106
|
+
h("option", { key: k, value: k }, t(`memory.explorer.date.${k}`))))
|
|
3107
|
+
),
|
|
2108
3108
|
h("div", { className: "mneme-xrow" },
|
|
2109
3109
|
h("span", { className: "mneme-xcount" }, t("memory.explorer.count").replace("{n}", String(visible.length))),
|
|
2110
3110
|
h("button", { className: "mneme-footbtn", onClick: () => setReloadKey((k) => k + 1) },
|
|
@@ -2137,7 +3137,42 @@ window.__ModuleLoader__.load({
|
|
|
2137
3137
|
h("span", { className: "mneme-xcount2" }, String(counts[key]))
|
|
2138
3138
|
))
|
|
2139
3139
|
),
|
|
2140
|
-
|
|
3140
|
+
viewMode === "cards"
|
|
3141
|
+
? h("div", { className: "mneme-cards" },
|
|
3142
|
+
loading
|
|
3143
|
+
? h("div", { className: "mneme-xempty", style: { gridColumn: "1 / -1" } }, "…")
|
|
3144
|
+
: visible.length === 0
|
|
3145
|
+
? h("div", { className: "mneme-xempty", style: { gridColumn: "1 / -1" } },
|
|
3146
|
+
h(Icon, { name: "inbox", size: 20, className: "mneme-xemptyico" }),
|
|
3147
|
+
t("memory.explorer.empty"))
|
|
3148
|
+
: sorted.map((m) => h("button", {
|
|
3149
|
+
key: m.id,
|
|
3150
|
+
type: "button",
|
|
3151
|
+
className: m.id === selectedId ? "mneme-card mneme-active" : "mneme-card",
|
|
3152
|
+
onClick: () => setSelectedId(m.id)
|
|
3153
|
+
},
|
|
3154
|
+
h("div", { className: "mneme-cardhead" },
|
|
3155
|
+
h("span", { className: "mneme-xdot", style: { color: memoryTypeColor(m.type) }, title: typeLabel(t, m.type), "aria-hidden": "true" }),
|
|
3156
|
+
h("span", null, typeLabel(t, m.type)),
|
|
3157
|
+
h("span", { style: { marginLeft: "auto", fontVariantNumeric: "tabular-nums" } }, formatRelativeTime(m.updated_at || m.created_at, t))
|
|
3158
|
+
),
|
|
3159
|
+
pendingIds.has(m.id) && h("span", { className: "mneme-badge mneme-badge--conflict", style: { alignSelf: "flex-start" } }, "⚠ ", t("memory.explorer.conflictBadge")),
|
|
3160
|
+
h("div", { className: "mneme-cardtitle" }, m.title || (m.content || "").slice(0, 60)),
|
|
3161
|
+
h("div", { className: "mneme-cardexcerpt" }, m.content || ""),
|
|
3162
|
+
h("div", { className: "mneme-cardfoot" },
|
|
3163
|
+
h("span", null, "★".repeat(Math.min(5, Math.max(0, m.importance || 0)))),
|
|
3164
|
+
m.source && h("span", { className: "mneme-cardsrc", title: m.source }, m.source)
|
|
3165
|
+
)
|
|
3166
|
+
)),
|
|
3167
|
+
h("div", { ref: moreRef, className: "mneme-xmore", style: { gridColumn: "1 / -1" } },
|
|
3168
|
+
canLoadMore
|
|
3169
|
+
? h("button", { className: "mneme-footbtn", disabled: loadingMore, onClick: loadMore },
|
|
3170
|
+
loadingMore ? "…" : `${t("memory.explorer.loadMore")}(${items.length}/${total})`)
|
|
3171
|
+
: (!loading && visible.length > 0 && !q && !remoteItems
|
|
3172
|
+
? h("span", { className: "mneme-xcount" }, `${items.length} / ${total}`)
|
|
3173
|
+
: null))
|
|
3174
|
+
)
|
|
3175
|
+
: h("div", { className: "mneme-xbrowse" },
|
|
2141
3176
|
h("div", { className: "mneme-xtree" },
|
|
2142
3177
|
h("div", { className: "mneme-xcolhead" }, t("memory.explorer.timeline")),
|
|
2143
3178
|
loading
|
|
@@ -2202,94 +3237,106 @@ window.__ModuleLoader__.load({
|
|
|
2202
3237
|
: null)
|
|
2203
3238
|
)
|
|
2204
3239
|
),
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
h("span", { title: formatDate(selected.updated_at) }, `${t("memory.explorer.updated")}: ${formatRelativeTime(selected.updated_at, t)}`)
|
|
2220
|
-
),
|
|
2221
|
-
Array.isArray(selected.tags) && selected.tags.length > 0 && h("div", { className: "mneme-xdmeta" },
|
|
2222
|
-
h("span", null, `${t("memory.explorer.tags")}: ${selected.tags.join(" · ")}`)
|
|
2223
|
-
),
|
|
2224
|
-
h("div", { className: "mneme-xdcontent" }, selected.content),
|
|
2225
|
-
h("div", { className: "mneme-xdactions" },
|
|
2226
|
-
h("button", { className: "mneme-footbtn", onClick: copyContent },
|
|
2227
|
-
h(Icon, { name: copied ? "check" : "copy", size: 12 }),
|
|
2228
|
-
copied ? t("memory.explorer.copied") : t("memory.explorer.copy")),
|
|
2229
|
-
// two-step delete: red outline arms, solid red commits
|
|
2230
|
-
confirmDelete
|
|
2231
|
-
? h(react.Fragment, null,
|
|
2232
|
-
h("button", {
|
|
2233
|
-
className: "mneme-btn mneme-btndangerconfirm",
|
|
2234
|
-
disabled: deleting,
|
|
2235
|
-
onClick: deleteSelected
|
|
2236
|
-
}, t("memory.explorer.confirmDelete")),
|
|
2237
|
-
h("button", {
|
|
2238
|
-
className: "mneme-btn",
|
|
2239
|
-
onClick: () => setConfirmDelete(false)
|
|
2240
|
-
}, t("memory.explorer.cancel"))
|
|
2241
|
-
)
|
|
2242
|
-
: h("button", {
|
|
2243
|
-
className: "mneme-btn mneme-btndanger",
|
|
2244
|
-
onClick: () => setConfirmDelete(true)
|
|
2245
|
-
}, t("memory.explorer.delete")),
|
|
2246
|
-
deleteError && h("span", { style: { fontSize: 12, color: "var(--dsw-alias-state-error,#c33)" } },
|
|
2247
|
-
t("memory.explorer.deleteFailed"))
|
|
2248
|
-
)
|
|
2249
|
-
)
|
|
2250
|
-
)
|
|
2251
|
-
: h("div", { className: "mneme-xempty" },
|
|
2252
|
-
deleteMsg && h("div", { className: "mneme-saved", style: { marginBottom: 6 } }, t("memory.explorer.deleted")),
|
|
2253
|
-
t("memory.explorer.emptyDetail"))
|
|
2254
|
-
)
|
|
2255
|
-
)
|
|
3240
|
+
),
|
|
3241
|
+
// 详情抽屉:卡片/时间线共用,选中即从右侧滑出(替代旧底部面板)。
|
|
3242
|
+
// 两步删除确认在抽屉内完成;编辑/归档经 /update 落库并同步本地视图。
|
|
3243
|
+
selected && h(MemoryDrawer, {
|
|
3244
|
+
key: selected.id,
|
|
3245
|
+
t,
|
|
3246
|
+
memory: selected,
|
|
3247
|
+
conflict: pendingIds.has(selected.id),
|
|
3248
|
+
deleting,
|
|
3249
|
+
deleteError,
|
|
3250
|
+
onClose: () => setSelectedId(null),
|
|
3251
|
+
onDelete: deleteById,
|
|
3252
|
+
onSaved: applyMemoryUpdate
|
|
3253
|
+
})
|
|
2256
3254
|
),
|
|
2257
3255
|
view === "entity" && h(EntityPanel, { t, focusEntity: graphFocus, onJumpMemory: jumpToMemory }),
|
|
2258
3256
|
view === "status" && h(StatusPanel, { t }),
|
|
2259
3257
|
view === "settings" && h("div", { className: "mneme-set" },
|
|
2260
3258
|
h("div", { className: "mneme-set-inner" }, h(SettingsContent, { t }))
|
|
2261
|
-
)
|
|
3259
|
+
),
|
|
3260
|
+
toastMsg && h("div", { className: "mneme-toast", role: "status" }, toastMsg)
|
|
2262
3261
|
);
|
|
2263
3262
|
}
|
|
2264
3263
|
|
|
2265
|
-
// --- Sidebar
|
|
2266
|
-
//
|
|
2267
|
-
//
|
|
2268
|
-
//
|
|
2269
|
-
//
|
|
2270
|
-
|
|
2271
|
-
|
|
3264
|
+
// --- Sidebar entry: 工作区上方的记忆按钮 ---
|
|
3265
|
+
// 注册仍在 sidebar.footer.action(list 插槽)——它既是 React 树的挂载
|
|
3266
|
+
// 锚点,也是 portal 失效时的原位回退。真实按钮 portal 插到 regionArea
|
|
3267
|
+
// (工作区列表容器)之前,即「新会话」按钮下方、工作区上方。
|
|
3268
|
+
// 几何对齐方案:读取宿主「新会话」按钮的实时 className 原样套用,再用
|
|
3269
|
+
// 我们的修饰类覆盖配色(次级观感)。这样展开态与收起态(rail)都直接
|
|
3270
|
+
// 继承宿主自己的盒模型与间距——宿主改版/缩进动画零位移,无需硬编码。
|
|
3271
|
+
// 宿主结构异常时约 2 秒后放弃 portal,footer 回退按钮保持可用。
|
|
3272
|
+
function SidebarFallbackTrigger({ wide, t }) {
|
|
2272
3273
|
return h("button", {
|
|
2273
3274
|
type: "button",
|
|
2274
3275
|
className: wide ? "mneme-trigger" : "mneme-trigger mneme-rail",
|
|
2275
3276
|
"aria-label": t("memory.sidebar.aria"),
|
|
2276
|
-
title: t("memory.
|
|
2277
|
-
onClick:
|
|
2278
|
-
activateExplorerTab(t("memory.view.label")).then((ok) => { if (!ok) setOpen(true); });
|
|
2279
|
-
}
|
|
3277
|
+
title: t("memory.panel.open"),
|
|
3278
|
+
onClick: openLibrary
|
|
2280
3279
|
},
|
|
2281
3280
|
h(IconArchiveOutline20, { size: wide ? 16 : 18 }),
|
|
2282
3281
|
wide && h("span", { className: "mneme-trigger-label" }, t("memory.panel.open"))
|
|
2283
3282
|
);
|
|
2284
3283
|
}
|
|
2285
3284
|
|
|
3285
|
+
function SidebarTopEntry({ wide, t, fallback }) {
|
|
3286
|
+
const [host, setHost] = useState(null);
|
|
3287
|
+
const [nativeCls, setNativeCls] = useState("");
|
|
3288
|
+
useEffect(() => {
|
|
3289
|
+
if (!reactDom || typeof document === "undefined") return undefined;
|
|
3290
|
+
let tries = 0, timer = null, created = null;
|
|
3291
|
+
const attempt = () => {
|
|
3292
|
+
const region = document.querySelector('[class*="regionArea"]');
|
|
3293
|
+
if (region && region.parentElement) {
|
|
3294
|
+
// 「新会话」按钮与 regionArea 同级且紧邻其前,借它的类名获得
|
|
3295
|
+
// 与原生侧边栏项完全一致的盒模型(含收起态 rail 几何)。
|
|
3296
|
+
const ns = region.parentElement.querySelector('[class*="newSession"]')
|
|
3297
|
+
|| region.previousElementSibling;
|
|
3298
|
+
created = document.createElement("div");
|
|
3299
|
+
created.dataset.pluginEntry = "@modusensus/dsh-mneme";
|
|
3300
|
+
region.parentElement.insertBefore(created, region);
|
|
3301
|
+
setHost(created);
|
|
3302
|
+
setNativeCls(ns?.className || "");
|
|
3303
|
+
return;
|
|
3304
|
+
}
|
|
3305
|
+
if (++tries > 40) return; // 放弃 portal,footer 回退保持可用
|
|
3306
|
+
timer = setTimeout(attempt, 50);
|
|
3307
|
+
};
|
|
3308
|
+
attempt();
|
|
3309
|
+
return () => {
|
|
3310
|
+
clearTimeout(timer);
|
|
3311
|
+
if (created && created.parentElement) created.parentElement.removeChild(created);
|
|
3312
|
+
};
|
|
3313
|
+
}, []);
|
|
3314
|
+
if (!host) return fallback;
|
|
3315
|
+
// wrapper 用 display:contents 隐身:我们的按钮成为侧边栏弹性布局的
|
|
3316
|
+
// 直接子元素,与「新会话」按钮同级同距;展开/收起只切换图标与文案。
|
|
3317
|
+
return reactDom.createPortal(
|
|
3318
|
+
h("div", { className: "mneme-topentry", style: { display: "contents" } },
|
|
3319
|
+
h("button", {
|
|
3320
|
+
type: "button",
|
|
3321
|
+
className: `${nativeCls} mneme-topentry-native`.trim(),
|
|
3322
|
+
"aria-label": t("memory.sidebar.aria"),
|
|
3323
|
+
title: t("memory.panel.open"),
|
|
3324
|
+
onClick: openLibrary
|
|
3325
|
+
},
|
|
3326
|
+
h(IconArchiveOutline20, { size: wide ? 15 : 18 }),
|
|
3327
|
+
wide && h("span", { className: "mneme-topentry-label" }, t("memory.panel.open"))
|
|
3328
|
+
)
|
|
3329
|
+
), host);
|
|
3330
|
+
}
|
|
3331
|
+
|
|
2286
3332
|
function apply(ctx) {
|
|
2287
3333
|
ctx.effect(() => ctx.locale.register(NS, dictionaries), "dsh-mneme: dictionaries");
|
|
2288
3334
|
|
|
2289
|
-
//
|
|
2290
|
-
//
|
|
2291
|
-
//
|
|
2292
|
-
//
|
|
3335
|
+
// 记忆库唯一入口:sidebar.footer.action 注册作为锚点与回退;真实按钮
|
|
3336
|
+
// 由 SidebarTopEntry portal 到侧边栏工作区上方。overlay 从这个常驻
|
|
3337
|
+
// 插槽挂载(portal 到 body),会话切换不影响它的开关状态。
|
|
3338
|
+
// v0.7.15 起不再注册 conversation.view tab——对话内嵌的面板被悬浮
|
|
3339
|
+
// 输入框遮挡、挤压会话布局,sheet 页是更舒服的承载方式。
|
|
2293
3340
|
ctx.slots.inject("sidebar.footer.action", () => {
|
|
2294
3341
|
const t = ctx.locale.bind(NS);
|
|
2295
3342
|
return ctx.slots.register({
|
|
@@ -2298,27 +3345,14 @@ window.__ModuleLoader__.load({
|
|
|
2298
3345
|
order: 0,
|
|
2299
3346
|
label: () => t("memory.panel.open")
|
|
2300
3347
|
}, (props) => h(react.Fragment, null,
|
|
2301
|
-
h(
|
|
2302
|
-
|
|
2303
|
-
|
|
3348
|
+
h(SidebarTopEntry, {
|
|
3349
|
+
wide: !!(props && props.wide),
|
|
3350
|
+
t,
|
|
3351
|
+
fallback: h(SidebarFallbackTrigger, { wide: !!(props && props.wide), t })
|
|
3352
|
+
}),
|
|
2304
3353
|
h(MemoryOverlay, { t })
|
|
2305
3354
|
));
|
|
2306
3355
|
});
|
|
2307
|
-
|
|
2308
|
-
// Register the memory library as a conversation view tab, beside
|
|
2309
|
-
// Chat / Trajectory. The view ignores its session props: the library
|
|
2310
|
-
// reads the memory store over HTTP. It hosts every memory feature —
|
|
2311
|
-
// browse, graph and settings — in the main content area.
|
|
2312
|
-
ctx.slots.inject("conversation.view", () => {
|
|
2313
|
-
const t = ctx.locale.bind(NS);
|
|
2314
|
-
return ctx.slots.register({
|
|
2315
|
-
name: "conversation.view",
|
|
2316
|
-
id: "dsh-mneme-memory",
|
|
2317
|
-
order: 30,
|
|
2318
|
-
locale: NS,
|
|
2319
|
-
label: () => t("memory.view.label")
|
|
2320
|
-
}, () => h(MemoryExplorer, { t }));
|
|
2321
|
-
});
|
|
2322
3356
|
}
|
|
2323
3357
|
|
|
2324
3358
|
exports.apply = apply;
|