@modusensus/dsh-mneme 0.13.0 → 0.14.0-beta.1
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.md +6 -4
- package/bin/dsh-mneme-choro.mjs +9 -1
- package/docs/CONFIGURATION.md +5 -3
- package/lib/api.js +211 -5
- package/lib/client.js +1240 -354
- package/lib/config.js +27 -0
- package/lib/dream/decisions.js +39 -1
- package/lib/dream/sleep.js +146 -6
- package/lib/dream/triage.js +139 -0
- package/lib/dream.js +123 -8
- package/lib/hot-memory.js +14 -2
- package/lib/index.js +31 -1
- package/lib/inject.js +14 -2
- package/lib/lang.js +11 -0
- package/lib/service.js +76 -4
- package/lib/settings.js +16 -1
- package/lib/store.js +238 -5
- package/lib/wiki/claims.js +267 -0
- package/lib/wiki/queries.js +168 -2
- package/lib/wiki/render.js +74 -0
- package/lib/wiki/t0.js +43 -6
- package/lib/wiki/t1.js +138 -31
- package/package.json +15 -1
- package/scripts/release-docs.mjs +7 -1
- package/scripts/release-notes.mjs +4 -1
- package/src/api.js +211 -5
- package/src/config.js +27 -0
- package/src/dream/decisions.js +39 -1
- package/src/dream/sleep.js +146 -6
- package/src/dream/triage.js +139 -0
- package/src/dream.js +123 -8
- package/src/hot-memory.js +14 -2
- package/src/index.js +31 -1
- package/src/inject.js +14 -2
- package/src/lang.js +11 -0
- package/src/service.js +76 -4
- package/src/settings.js +16 -1
- package/src/store.js +238 -5
- package/src/wiki/claims.js +267 -0
- package/src/wiki/queries.js +168 -2
- package/src/wiki/render.js +74 -0
- package/src/wiki/t0.js +43 -6
- package/src/wiki/t1.js +138 -31
- package/test/api.test.js +11 -1
- package/test/client.test.js +275 -34
- package/test/conflict-ledger-enforce.test.js +452 -0
- package/test/conflict-ledger.test.js +242 -0
- package/test/contrast.test.js +124 -12
- package/test/dream-baseline-clamp.test.js +174 -0
- package/test/graph-api.test.js +124 -0
- package/test/hot-memory.test.js +48 -0
- package/test/inject-parent-gate.test.js +6 -3
- package/test/inject.test.js +59 -0
- package/test/panel-chart-scale.test.js +90 -0
- package/test/panel-tokens.test.js +245 -0
- package/test/release-docs.test.js +20 -0
- package/test/release-notes.test.js +25 -0
- package/test/security-doc.test.js +10 -0
- package/test/sleep.test.js +37 -0
- package/test/wiki-api.test.js +110 -3
- package/test/wiki-claims.test.js +194 -0
- package/test/wiki-provenance.test.js +202 -0
- package/test/wiki-queries.test.js +121 -0
- package/test/wiki-t0.test.js +77 -1
- package/test/wiki-t1.test.js +226 -1
package/lib/client.js
CHANGED
|
@@ -153,6 +153,11 @@ window.__ModuleLoader__.load({
|
|
|
153
153
|
// 所以这条表与用法必须同处出现(见 test/client.test.js 的图标名锁)。
|
|
154
154
|
star: [["polygon", { points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" }]],
|
|
155
155
|
persona: [["path", { d: "M12 3a7 7 0 0 0-7 7c0 4 3 5 3 8h8c0-3 3-4 3-8a7 7 0 0 0-7-7z" }], ["path", { d: "M10 11h.01" }], ["path", { d: "M14 11h.01" }], ["path", { d: "M9.5 15c1.6 1 3.4 1 5 0" }]],
|
|
156
|
+
// 情绪页与日记页的页签图标(Lucide 数据,同上 ISC 惯例内联)。
|
|
157
|
+
// 之前两者都掉进三元表达式的 else 分支、跟设置页共用齿轮,页签只能靠
|
|
158
|
+
// 文字区分;这里补齐名字,并在下面用 TAB_ICONS 把 key 与图标绑死。
|
|
159
|
+
heart: [["path", { d: "M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z" }]],
|
|
160
|
+
book: [["path", { d: "M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" }]],
|
|
156
161
|
};
|
|
157
162
|
const Icon = ({ name, size = 16, className }) => {
|
|
158
163
|
const parts = ICON_PATHS[name];
|
|
@@ -352,6 +357,35 @@ window.__ModuleLoader__.load({
|
|
|
352
357
|
"wiki.t1.failedCount": "失败",
|
|
353
358
|
"wiki.stale": "代码已变",
|
|
354
359
|
"wiki.stale.hint": "上次生成之后仓库代码变过,页面可能和现状对不上:点「重建骨架」刷新。",
|
|
360
|
+
// 证据复核(Grounded Claims):不重跑 T1 也能知道「哪几条结论被改动碰到了」。
|
|
361
|
+
// 文案刻意把「算不出来」和「一条都没事」写成两句不同的话——这两种状态在
|
|
362
|
+
// 屏幕上长得一样的话,用户会把「没算」当成「没问题」,那正是 #607 的病。
|
|
363
|
+
"wiki.drift": "证据复核",
|
|
364
|
+
"wiki.drift.hint": "用 git diff 比对每条结论记下的文件与行号:只有被改动碰到的才标出来,不重跑模型。",
|
|
365
|
+
"wiki.drift.loading": "正在比对…",
|
|
366
|
+
"wiki.drift.summary": "{pages} 页 · {claims} 条结论 · {risk} 条需要复核",
|
|
367
|
+
"wiki.drift.clean": "基线就是当前 HEAD:所有结论的锚点都还对得上。",
|
|
368
|
+
"wiki.drift.none": "这次改动没碰到任何结论的锚点。",
|
|
369
|
+
"wiki.drift.gitUnreadable": "读不到这个仓库的 git 信息(不是 git 目录或不可读),没法比对。",
|
|
370
|
+
"wiki.drift.anchorStale": "上次生成记下的基线提交在 git 里已经找不到了(可能被 force-push 或浅克隆截断):无法比对,建议重建骨架。",
|
|
371
|
+
"wiki.drift.blocked": "比对失败",
|
|
372
|
+
"wiki.drift.touched": "改动碰到这行",
|
|
373
|
+
"wiki.drift.shifted": "上方有增删,行号挪了 {n}",
|
|
374
|
+
"wiki.drift.deleted": "文件已删",
|
|
375
|
+
"wiki.drift.unverifiable": "这条没记行号,无法定位",
|
|
376
|
+
"wiki.drift.inconsistent": "正文有语义段、库里却没有结论记录(上次 T1 没写完),这页证据无法核对。",
|
|
377
|
+
// 语义段 provenance(①):每页的结论是「对着哪个提交」读出来的。不说清这一点,
|
|
378
|
+
// 「证据复核」就只是在和某个不明时点比——页越晚富化,误报越多。
|
|
379
|
+
"wiki.drift.verified": "对着 {sha} 验证",
|
|
380
|
+
"wiki.drift.verifiedUnknown": "这页没记下是哪个提交验证的(早期富化没有记录),所以判不出它有没有过时。重跑一次 T1 就会记上。",
|
|
381
|
+
"wiki.drift.verifiedApprox": "这页是早期富化的,没记下自己的验证点;下面按全仓基线 {sha} 比对(起点比实际更早,只会多报不会漏报)。",
|
|
382
|
+
"wiki.drift.anchorStalePage": "这页记下的验证提交在 git 里已经找不到了(force-push 或浅克隆截断),这页没法比对。",
|
|
383
|
+
"wiki.drift.undecidable": "{n} 页的验证基线不可用,没有参与比对",
|
|
384
|
+
// 自动刷新被主动跳过(仓库里有已富化的页)。这句话必须说的是「我没做,因为
|
|
385
|
+
// 做了会丢东西」,而不是「刷新失败」——失败是另一种情况,另一种文案。
|
|
386
|
+
"wiki.autoRefresh.skipped": "自动刷新这次没有做:这仓库有 {n} 页已经富化过,重跑骨架会把语义段和结论一起清掉。要新快照请手动点「重建骨架」(会先告诉你丢哪几页)。",
|
|
387
|
+
"wiki.autoRefresh.skippedGeneric": "自动刷新这次没有做:这仓库已有富化过的页,重跑骨架会把语义段和结论一起清掉。要新快照请手动点「重建骨架」。",
|
|
388
|
+
"wiki.autoRefresh.failed": "自动刷新试过但失败了",
|
|
355
389
|
"wiki.run.pathRequired": "先填仓库目录的绝对路径",
|
|
356
390
|
"wiki.run.failed": "生成失败",
|
|
357
391
|
"wiki.run.notAbsolute": "要给绝对路径(例如 D:\\code\\my-repo):相对路径会按服务端进程的当前目录解析,同一个请求换个启动位置就落到别的目录。",
|
|
@@ -526,16 +560,16 @@ window.__ModuleLoader__.load({
|
|
|
526
560
|
"memory.settings.factsRunStatusOk": "成功",
|
|
527
561
|
"memory.settings.factsRunStatusNoop": "无变更",
|
|
528
562
|
"memory.settings.factsRunStatusFailed": "失败",
|
|
529
|
-
"memory.features.personaEnabled": "
|
|
530
|
-
"memory.features.personaEnabled.hint": "
|
|
531
|
-
"memory.features.personaStrictAddressing": "
|
|
532
|
-
"memory.features.personaStrictAddressing.hint": "
|
|
563
|
+
"memory.features.personaEnabled": "人格注入",
|
|
564
|
+
"memory.features.personaEnabled.hint": "把人格卡注入每轮对话;与卡内开关同时开才生效。",
|
|
565
|
+
"memory.features.personaStrictAddressing": "严格寻址",
|
|
566
|
+
"memory.features.personaStrictAddressing.hint": "身份没有对应人格卡时不注入,默认卡只兜解析不到的会话。",
|
|
533
567
|
"memory.features.profileExtractEnabled": "画像提取草稿",
|
|
534
|
-
"memory.features.profileExtractEnabled.hint": "
|
|
568
|
+
"memory.features.profileExtractEnabled.hint": "从记忆生成「Agent 眼中的你」草稿,只出副本。",
|
|
535
569
|
"memory.features.profileFactsEnabled": "自动事实",
|
|
536
|
-
"memory.features.profileFactsEnabled.hint": "
|
|
537
|
-
"memory.features.chorographyEnabled": "
|
|
538
|
-
"memory.features.chorographyEnabled.hint": "
|
|
570
|
+
"memory.features.profileFactsEnabled.hint": "每次巩固后自动维护「Agent 眼中的你」事实清单。",
|
|
571
|
+
"memory.features.chorographyEnabled": "仓库地志",
|
|
572
|
+
"memory.features.chorographyEnabled.hint": "把仓库读成一份带页码的志书,四个入口随开随用。",
|
|
539
573
|
"memory.features.chorographyInject": "开局注入档位",
|
|
540
574
|
"memory.features.chorographyInject.hint": "每轮往上下文里塞地志提纲的份量:light 只列文件数最多的 6 个模块(约 8 行)、full 列全部模块、off 不注入(工具与面板页仍可用)。只注模块名 / 文件数 / 页状态 / 页相对路径,不含正文;正文要模型自己用 choro_read_page 取。轻量档预设压成 off。",
|
|
541
575
|
"memory.features.chorographyInject.off": "off(不注入)",
|
|
@@ -546,7 +580,7 @@ window.__ModuleLoader__.load({
|
|
|
546
580
|
"memory.features.chorographyT1FromPanel": "面板可发起 T1",
|
|
547
581
|
"memory.features.chorographyT1FromPanel.hint": "允许面板「仓库地志」页上的「补语义正文(T1)」按钮起一个 headless 会话逐模块补语义段。默认开:按钮是人主动点的,不是后台行为。关掉后按钮仍在,但点了会提示去命令行跑。",
|
|
548
582
|
"memory.features.kbEnabled": "知识库",
|
|
549
|
-
"memory.features.kbEnabled.hint": "
|
|
583
|
+
"memory.features.kbEnabled.hint": "外部资料切块索引,供检索与注入;面板多一页。",
|
|
550
584
|
"memory.features.group.kb": "知识库",
|
|
551
585
|
"memory.settings.rules": "规则",
|
|
552
586
|
"memory.settings.rulesHint": "给 Agent 立下必须遵守的规矩,随时增删,下一轮即生效。",
|
|
@@ -569,6 +603,7 @@ window.__ModuleLoader__.load({
|
|
|
569
603
|
"memory.explorer.tabSettings": "设置",
|
|
570
604
|
"memory.explorer.tabStatus": "状态",
|
|
571
605
|
"memory.explorer.tabPersona": "人格",
|
|
606
|
+
"memory.explorer.tabAffective": "情绪",
|
|
572
607
|
"memory.entities.count": "{n} 个实体",
|
|
573
608
|
"memory.entities.countSampled": "列表只列最近 {shown} 个;上方总数是全库",
|
|
574
609
|
"memory.entities.pick": "从左侧选择一个实体,查看它的属性与关系",
|
|
@@ -618,6 +653,18 @@ window.__ModuleLoader__.load({
|
|
|
618
653
|
"memory.time.minutes": "{n}分钟前",
|
|
619
654
|
"memory.time.hours": "{n}小时前",
|
|
620
655
|
"memory.time.days": "{n}天前",
|
|
656
|
+
"memory.affective.title": "情绪归因",
|
|
657
|
+
"memory.affective.sub": "按「谁 · 什么情绪 · 对什么」汇总,稳定倾向与单独一次分开列。",
|
|
658
|
+
"memory.affective.loading": "读取中…",
|
|
659
|
+
"memory.affective.error": "没读到数据 —— 这不等于「没有记录」,是这次请求失败了。",
|
|
660
|
+
"memory.affective.empty": "库里还没有情绪边。开了情感相位并跑过一轮夜间巩固,这里才会有内容。",
|
|
661
|
+
"memory.affective.occasion": "一次场合",
|
|
662
|
+
"memory.affective.trait": "稳定倾向",
|
|
663
|
+
"memory.affective.sessions": "跨 {n} 个独立会话",
|
|
664
|
+
"memory.affective.evidence": "{n} 条证据",
|
|
665
|
+
"memory.affective.lastSeen": "最近 {time}",
|
|
666
|
+
"memory.affective.truncated": "只列了最近 {n} 条边,更早的没进这一页",
|
|
667
|
+
"memory.affective.skipped": "{n} 条边没有情绪名,未计入",
|
|
621
668
|
"memory.graph.title": "记忆图谱",
|
|
622
669
|
"memory.graph.aria": "记忆图谱",
|
|
623
670
|
"memory.graph.placeholder": "输入实体名,查看关联网络…",
|
|
@@ -650,6 +697,16 @@ window.__ModuleLoader__.load({
|
|
|
650
697
|
"memory.graph.src.confirmed": "人工确认",
|
|
651
698
|
"memory.graph.src.tag": "标签共现",
|
|
652
699
|
"memory.graph.src.llm": "模型抽取",
|
|
700
|
+
// #619 可见化 B:情感边(affective_context)在侧卡上要能不点进去就看懂。
|
|
701
|
+
// 两档必须用不同的词:occasion 是「这一次场合观察到的情绪」,trait 是
|
|
702
|
+
// 「跨若干独立场合反复出现、已被铸成稳定倾向」。只显示「焦虑」会让两者
|
|
703
|
+
// 看起来一样,而它们回答的是完全不同的问题(这次怎么了 / 这个人是什么样)。
|
|
704
|
+
"memory.graph.emotion": "情绪 {e}",
|
|
705
|
+
"memory.graph.affectiveOccasion": "这一次的场合",
|
|
706
|
+
"memory.graph.affectiveOccasions": "已跨 {n} 个场合",
|
|
707
|
+
"memory.graph.affectiveTrait": "稳定倾向 · 跨 {n} 个独立场合",
|
|
708
|
+
"memory.graph.affectiveEvidence": "{n} 条证据",
|
|
709
|
+
"memory.graph.affectiveLegacy": "旧版情感边(没有场合记录)",
|
|
653
710
|
"memory.settings.vectorTitle": "向量搜索",
|
|
654
711
|
"memory.settings.vectorHint": "搜索不再只认字面:接入任意 OpenAI 兼容的 embedding 接口后,意思相近的记忆也能被召回。",
|
|
655
712
|
"memory.settings.vectorEnabled": "启用向量搜索",
|
|
@@ -737,40 +794,46 @@ window.__ModuleLoader__.load({
|
|
|
737
794
|
"memory.tab.document": "文档",
|
|
738
795
|
"memory.features.title": "功能开关",
|
|
739
796
|
"memory.features.desc": "控制插件在后台做哪些事。先看「常用」里的总开关就够了,其余项收在「全部设置」里。改动即时保存,重启 DSH 后生效。",
|
|
740
|
-
// Issue #427
|
|
741
|
-
//
|
|
742
|
-
//
|
|
797
|
+
// Issue #427:组名说人话、术语留在 hint。#427 revamp(2026-11-11)按
|
|
798
|
+
// 「防线 / 写入 / 检索 / 个性化」重排:core 里「拦什么、记什么」的开关
|
|
799
|
+
// 与行为开关混在一起、enhance 里检索旋钮与人格画像混在一起,是设置页
|
|
800
|
+
// 「难找、难看」的主因(产品口径:每组 ≤6 行、性质一致)。
|
|
743
801
|
// group.basic 是首屏层:新用户不读文档就该答得出「会不会自动记」;
|
|
744
802
|
// 「别的会话能不能看到」由同在首屏的 scope 组回答。
|
|
745
|
-
"memory.features.group.basic": "
|
|
746
|
-
|
|
803
|
+
"memory.features.group.basic": "常用",
|
|
804
|
+
// group.ext 是 v0.13.x 起的独立分组:mneme 本体只做记忆,仓库地志 /
|
|
805
|
+
// 知识库 / 日记本是三个可选子系统。它们是「装不装这个子系统」而不是
|
|
806
|
+
// 「每轮付多少」(#607 的「知识库没成型」一半是入口问题),故提到
|
|
807
|
+
// 首屏单列一组。
|
|
808
|
+
"memory.features.group.ext": "扩展功能",
|
|
809
|
+
"memory.features.group.safety": "防线与审计",
|
|
810
|
+
"memory.features.group.pipeline": "蒸馏与写入",
|
|
811
|
+
"memory.features.group.retrieve": "检索与注入",
|
|
812
|
+
"memory.features.group.persona": "个性化",
|
|
747
813
|
// group.mo 是 v0.9.0 的独立分组(座位闸门,不是行为开关),现已随 M.O.
|
|
748
814
|
// 下架从 FEATURE_GROUPS 摘掉;这条文案留着是为了恢复时不必再想措辞。
|
|
749
815
|
"memory.features.group.mo": "它的工作流(M.O.)",
|
|
750
|
-
"memory.features.group.
|
|
751
|
-
"memory.features.group.
|
|
752
|
-
"memory.features.group.scope": "不同会话之间",
|
|
816
|
+
"memory.features.group.dream": "巩固与睡眠",
|
|
817
|
+
"memory.features.group.scope": "会话隔离",
|
|
753
818
|
"memory.features.group.advanced": "高级",
|
|
754
819
|
// 「全部设置」= 第一层折叠(issue #427 建议 1),常显只留 basic + scope
|
|
755
820
|
// 两组;「高级」是它内部的第二层(进阶 7 项)。
|
|
756
821
|
"memory.features.allToggle": "全部设置",
|
|
757
|
-
"memory.features.advancedToggle": "更少用的
|
|
822
|
+
"memory.features.advancedToggle": "更少用的 9 个开关",
|
|
758
823
|
"memory.features.restartHint": "重启 DSH 后生效",
|
|
759
824
|
"memory.features.loadFailed": "加载失败",
|
|
760
825
|
"memory.features.saveFailed": "保存失败",
|
|
761
826
|
// Issue #427 建议 2:条目名给「打开会怎样、关掉会怎样」,术语放末尾。
|
|
762
827
|
// 括号里的英文是设置项键名,末尾的「(BM25)」才是术语。
|
|
763
|
-
"memory.features.autoInject": "
|
|
764
|
-
"memory.features.autoInject.hint": "
|
|
765
|
-
"memory.features.injectGuidanceEnabled": "
|
|
766
|
-
"memory.features.injectGuidanceEnabled.hint": "
|
|
767
|
-
"memory.features.continuityRescueEnabled": "
|
|
768
|
-
"memory.features.continuityRescueEnabled.hint": "
|
|
828
|
+
"memory.features.autoInject": "自动注入旧记忆",
|
|
829
|
+
"memory.features.autoInject.hint": "每轮自动把相关记忆带给 Agent,不用它自己查。",
|
|
830
|
+
"memory.features.injectGuidanceEnabled": "教 Agent 用记忆",
|
|
831
|
+
"memory.features.injectGuidanceEnabled.hint": "在记忆工具说明里写清何时查、何时写。",
|
|
832
|
+
"memory.features.continuityRescueEnabled": "压缩前存快照",
|
|
833
|
+
"memory.features.continuityRescueEnabled.hint": "长对话压缩前保存关键上下文,不被压掉。",
|
|
769
834
|
"memory.features.parentOff": "上一项关闭时不生效",
|
|
770
835
|
// 「默认 / 已改」徽标:overrides 里没有这个键 = 值来自 config schema 的
|
|
771
836
|
// 默认值,一条命令就能退回。徽标让这件事不必去读配置文件才成立。
|
|
772
|
-
"memory.features.tagDefault": "默认",
|
|
773
|
-
"memory.features.tagDefault.hint": "这一项用的是出厂默认值,没有被你改过",
|
|
774
837
|
"memory.features.tagChanged": "已改",
|
|
775
838
|
"memory.features.tagChanged.hint": "你改过这一项,它不再用默认值",
|
|
776
839
|
// 「待重启」是第三态,不是「已改」的加强版:#481 之前面板只有「已改」,
|
|
@@ -778,14 +841,16 @@ window.__ModuleLoader__.load({
|
|
|
778
841
|
"memory.features.tagPending": "待重启",
|
|
779
842
|
"memory.features.tagPending.hint": "这一项已存进配置,但本次启动还没用上——重启 DSH(或在插件管理里关开一次本插件)后才生效",
|
|
780
843
|
"memory.features.pendingRestartNotice": "有改动还没生效:标着「待重启」的开关要等重启 DSH 后才起作用(在插件管理里把本插件关开一次也行)。",
|
|
781
|
-
"memory.features.autoSummarize": "
|
|
782
|
-
"memory.features.autoSummarize.hint": "
|
|
783
|
-
"memory.features.hotMemoryEnabled": "
|
|
784
|
-
"memory.features.hotMemoryEnabled.hint": "
|
|
785
|
-
"memory.features.
|
|
786
|
-
"memory.features.
|
|
787
|
-
"memory.features.
|
|
788
|
-
"memory.features.
|
|
844
|
+
"memory.features.autoSummarize": "自动蒸馏对话",
|
|
845
|
+
"memory.features.autoSummarize.hint": "对话结束后自动提炼成长期记忆。",
|
|
846
|
+
"memory.features.hotMemoryEnabled": "带上最近几轮原话",
|
|
847
|
+
"memory.features.hotMemoryEnabled.hint": "注入附带最近几轮对话原文,不写进长期记忆。",
|
|
848
|
+
"memory.features.injectRotationTurns": "记忆换着来:最近几轮用过的先靠后",
|
|
849
|
+
"memory.features.injectRotationTurns.hint": "填 0–20:最近 N 轮已经注入过的记忆本轮不再优先,让排后面的也有露脸机会;0 = 关(每轮都取最相关的同一批,默认)。#637 的重复注入主要来自热记忆快照抖动(已修),这个窗口解决的是另一件事:同一批记忆长期占位",
|
|
850
|
+
"memory.features.injectTimePrefix": "注入日期星期",
|
|
851
|
+
"memory.features.injectTimePrefix.hint": "新会话第一轮带一次日期,「今天」更稳。",
|
|
852
|
+
"memory.features.entityExtractionEnabled": "实体抽取",
|
|
853
|
+
"memory.features.entityExtractionEnabled.hint": "每条新记忆抽出人物/项目/概念,长出关系图谱。",
|
|
789
854
|
"memory.features.entityExtractionProvider": "实体抽取 Provider",
|
|
790
855
|
"memory.features.entityExtractionModel": "实体抽取模型",
|
|
791
856
|
"memory.features.entityExtractionReasoning": "实体抽取思考强度",
|
|
@@ -801,14 +866,16 @@ window.__ModuleLoader__.load({
|
|
|
801
866
|
"memory.features.summarizeReasoningEffort.high": "高",
|
|
802
867
|
"memory.features.summarizeReasoningEffort.hint": "思考型模型建议选「低」或「关闭思考」,避免推理占满输出导致总结失败",
|
|
803
868
|
"memory.features.entityExtractionModelHint": "Provider / 模型留空 = 跟随主对话模型;思考强度选「跟随默认」= 服务商默认",
|
|
804
|
-
"memory.features.codingRetrospect": "
|
|
805
|
-
"memory.features.codingRetrospect.hint": "
|
|
806
|
-
"memory.features.rerankEnabled": "
|
|
807
|
-
"memory.features.rerankEnabled.hint": "
|
|
808
|
-
"memory.features.heatEnabled": "
|
|
809
|
-
"memory.features.heatEnabled.hint": "
|
|
810
|
-
"memory.features.
|
|
811
|
-
"memory.features.
|
|
869
|
+
"memory.features.codingRetrospect": "编码回溯",
|
|
870
|
+
"memory.features.codingRetrospect.hint": "整段编码过程一并回顾,沉淀踩坑与被否方案。",
|
|
871
|
+
"memory.features.rerankEnabled": "结果重排",
|
|
872
|
+
"memory.features.rerankEnabled.hint": "本地重排模型给召回结果重新打分,更准更慢。",
|
|
873
|
+
"memory.features.heatEnabled": "热度衰减",
|
|
874
|
+
"memory.features.heatEnabled.hint": "越久没用的记忆越靠后,像遗忘曲线。",
|
|
875
|
+
"memory.features.injectHeatEnabled": "注入热度排序",
|
|
876
|
+
"memory.features.injectHeatEnabled.hint": "注入排序乘上记忆热度;需先开「热度衰减」。",
|
|
877
|
+
"memory.features.documentMemoryEnabled": "文档只存摘要卡",
|
|
878
|
+
"memory.features.documentMemoryEnabled.hint": "长文档记忆只放摘要,正文按需读取。",
|
|
812
879
|
// --- v0.9.0 M.O.(Modus Operandi,beta)---
|
|
813
880
|
// M.O. = Modus Operandi,拉丁语「一个人一贯的做事方式」;也是 org 词根
|
|
814
881
|
// modus(modusensus)。界面主显示 M.O.,设置项 hint 第一句给全名。
|
|
@@ -884,10 +951,10 @@ window.__ModuleLoader__.load({
|
|
|
884
951
|
"memory.features.actionPipelineEnabled.hint": "只读视图:把真实会话里反复走通的动作连成一张图谱——会话头图标、右栏完整视图、每次运行的结算卡。看 ≠ 写:沉淀由「M.O. 自动沉淀」单独开,执行记账由 /mo-run 单独开。Modus Operandi(工作流)。重启 DSH 后生效",
|
|
885
952
|
"memory.features.actionNudgeEnabled": "自动沉淀工作流(beta)",
|
|
886
953
|
"memory.features.actionNudgeEnabled.hint": "开:每轮结束后看一眼这个项目自己的会话日志,把反复走通的动作挖成记忆库里的一条「提议」,等你确认才可能变成工作流。只产草稿、不自动启用、不往仓库里塞文件。挂热路径且要解压日志,轻量档会强制关掉。重启 DSH 后生效",
|
|
887
|
-
"memory.features.diaryEnabled": "
|
|
888
|
-
"memory.features.diaryEnabled.hint": "
|
|
889
|
-
"memory.features.diaryWriteEveryMinutes": "
|
|
890
|
-
"memory.features.diaryWriteEveryMinutes.hint": "
|
|
954
|
+
"memory.features.diaryEnabled": "日记本",
|
|
955
|
+
"memory.features.diaryEnabled.hint": "有人格的 agent 每天按真实对话写一篇日记。",
|
|
956
|
+
"memory.features.diaryWriteEveryMinutes": "自动写间隔(分钟)",
|
|
957
|
+
"memory.features.diaryWriteEveryMinutes.hint": "到点自动看一眼今天该不该写;0 = 只在聊完与重启时判。",
|
|
891
958
|
"memory.diary.title": "日记本",
|
|
892
959
|
"memory.diary.note": "写的是这个项目里真实发生过的事:没聊过就不写,不编。",
|
|
893
960
|
"memory.diary.write": "写今天",
|
|
@@ -911,8 +978,7 @@ window.__ModuleLoader__.load({
|
|
|
911
978
|
"memory.diary.bookLabel": "纸和字",
|
|
912
979
|
"memory.diary.book.plain": "素纸",
|
|
913
980
|
"memory.diary.book.ruled": "横线",
|
|
914
|
-
"memory.diary.book.grid": "
|
|
915
|
-
"memory.diary.book.kraft": "牛皮",
|
|
981
|
+
"memory.diary.book.grid": "点阵",
|
|
916
982
|
"memory.diary.count": "共 {n} 篇",
|
|
917
983
|
"memory.diary.writtenOk": "写好了:{path}",
|
|
918
984
|
"memory.diary.exportOk": "已存到 {path}",
|
|
@@ -926,28 +992,34 @@ window.__ModuleLoader__.load({
|
|
|
926
992
|
"memory.diary.status.written": "写了",
|
|
927
993
|
"memory.diary.status.skipped": "跳过",
|
|
928
994
|
"memory.diary.status.failed": "失败",
|
|
929
|
-
"memory.features.resilientModelDownload": "
|
|
930
|
-
"memory.features.resilientModelDownload.hint": "
|
|
931
|
-
"memory.features.searchSemanticDedup": "
|
|
932
|
-
"memory.features.searchSemanticDedup.hint": "
|
|
933
|
-
"memory.features.autoDream": "
|
|
934
|
-
"memory.features.autoDream.hint": "
|
|
935
|
-
"memory.features.sleepModeEnabled": "
|
|
936
|
-
"memory.features.sleepModeEnabled.hint": "
|
|
995
|
+
"memory.features.resilientModelDownload": "下载断点续传",
|
|
996
|
+
"memory.features.resilientModelDownload.hint": "模型下载中断后从断点续传并自动重试。",
|
|
997
|
+
"memory.features.searchSemanticDedup": "同义去重",
|
|
998
|
+
"memory.features.searchSemanticDedup.hint": "检索结果里意思相近的条目合并成一条。",
|
|
999
|
+
"memory.features.autoDream": "后台自动整理",
|
|
1000
|
+
"memory.features.autoDream.hint": "空闲时段自动巩固:去重、合并、归档。",
|
|
1001
|
+
"memory.features.sleepModeEnabled": "睡眠整理",
|
|
1002
|
+
"memory.features.sleepModeEnabled.hint": "空闲时段分层压缩与整理,白天少打扰。",
|
|
937
1003
|
// #619:情感归因是 sleep 的子相——父开关关着它就一定不跑,所以紧挨着摆。
|
|
938
|
-
"memory.features.sleepAffectiveEnabled": "
|
|
939
|
-
"memory.features.sleepAffectiveEnabled.hint": "
|
|
1004
|
+
"memory.features.sleepAffectiveEnabled": "情感归因",
|
|
1005
|
+
"memory.features.sleepAffectiveEnabled.hint": "整理时记下「谁在哪件事上什么心情」,反复出现才认。",
|
|
940
1006
|
// #427 建议 5:hybridInject / selectiveInjectEnabled / adaptiveThresholdEnabled
|
|
941
1007
|
// 是同一件事「带什么回来」的三档,相邻摆放并在 hint 里点明,不再摊成
|
|
942
1008
|
// 三个不相干的并列开关。
|
|
943
|
-
"memory.features.hybridInject": "
|
|
944
|
-
"memory.features.hybridInject.hint": "
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
"memory.features.
|
|
948
|
-
"memory.features.
|
|
949
|
-
"memory.features.
|
|
950
|
-
"memory.features.
|
|
1009
|
+
"memory.features.hybridInject": "混合注入",
|
|
1010
|
+
"memory.features.hybridInject.hint": "关键词 + 语义两路一起找,召回更全。",
|
|
1011
|
+
// 实体召回(#219)与图谱锚定(#24):进阶检索轴,默认关、依赖实体抽取
|
|
1012
|
+
// 产出(pipeline 组),收进「高级」第二层折叠。
|
|
1013
|
+
"memory.features.entityRecallEnabled": "实体召回",
|
|
1014
|
+
"memory.features.entityRecallEnabled.hint": "查询命中实体名时,把该实体挂联的记忆一起召回。",
|
|
1015
|
+
"memory.features.graphAnchoringEnabled": "图谱锚定",
|
|
1016
|
+
"memory.features.graphAnchoringEnabled.hint": "以命中实体为种子沿关系表级联扩散,把邻居记忆带进召回池。",
|
|
1017
|
+
"memory.features.selectiveInjectEnabled": "选择性注入",
|
|
1018
|
+
"memory.features.selectiveInjectEnabled.hint": "按相关性筛一遍再注入,上下文更干净。",
|
|
1019
|
+
"memory.features.adaptiveThresholdEnabled": "自适应阈值",
|
|
1020
|
+
"memory.features.adaptiveThresholdEnabled.hint": "召回质量差时放宽门槛、好时收紧。",
|
|
1021
|
+
"memory.features.reflectionUpdateEnabled": "允许改写旧记忆",
|
|
1022
|
+
"memory.features.reflectionUpdateEnabled.hint": "巩固可以改写合并已有条目,不只是增。",
|
|
951
1023
|
"memory.features.distill": "蒸馏与限流",
|
|
952
1024
|
"memory.features.distillRateLimitIntervalMs": "蒸馏放行间隔 (ms)",
|
|
953
1025
|
"memory.features.distillRateLimitRetries": "429 重试次数",
|
|
@@ -958,42 +1030,47 @@ window.__ModuleLoader__.load({
|
|
|
958
1030
|
// hint 必须写清两件事:这是**原始对话**落盘(隐私面),以及天数只管
|
|
959
1031
|
// 启动期裁剪(reclaim 走另一条 --older-than,不读这个键)。
|
|
960
1032
|
"memory.features.distillRecords.enabled": "留存蒸馏原文",
|
|
961
|
-
"memory.features.distillRecords.enabled.hint": "
|
|
1033
|
+
"memory.features.distillRecords.enabled.hint": "蒸馏时落盘窗口原文并记来源,私密内容不落。",
|
|
962
1034
|
"memory.features.distillRecords.retentionDays": "原文保留天数",
|
|
963
|
-
"memory.features.distillRecords.retentionDays.hint": "1–3650
|
|
1035
|
+
"memory.features.distillRecords.retentionDays.hint": "1–3650 天,只管启动期裁剪。",
|
|
964
1036
|
"memory.features.codingBoostFactor": "编码记忆加权",
|
|
965
|
-
"memory.features.memoryQualityFilter.enabled": "
|
|
966
|
-
"memory.features.memoryQualityFilter.enabled.hint": "
|
|
967
|
-
"memory.features.llmAudit.enabled": "
|
|
968
|
-
"memory.features.llmAudit.enabled.hint": "
|
|
1037
|
+
"memory.features.memoryQualityFilter.enabled": "质量过滤",
|
|
1038
|
+
"memory.features.memoryQualityFilter.enabled.hint": "低价值记忆自动归档、注入时降权。",
|
|
1039
|
+
"memory.features.llmAudit.enabled": "调用审计",
|
|
1040
|
+
"memory.features.llmAudit.enabled.hint": "后台模型调用与 token 消耗都有账可查。",
|
|
969
1041
|
// #254:写入准入两层。检测关时整条路径不碰;检测开、拦截关 = 仅告警 +
|
|
970
1042
|
// 留审计;两个都开才真拦。文案要写清「只留审计」与「真拦下」的区别,
|
|
971
1043
|
// 否则操作者会以为打开检测就已经在拦。
|
|
972
|
-
"memory.features.writeAdmission.enabled": "
|
|
973
|
-
"memory.features.writeAdmission.enabled.hint": "
|
|
974
|
-
"memory.features.writeAdmission.enforce": "
|
|
975
|
-
"memory.features.writeAdmission.enforce.hint": "
|
|
976
|
-
"memory.features.preInjectGate.enabled": "
|
|
977
|
-
"memory.features.preInjectGate.enabled.hint": "
|
|
978
|
-
"memory.features.preInjectGate.enforce": "
|
|
979
|
-
"memory.features.preInjectGate.enforce.hint": "
|
|
1044
|
+
"memory.features.writeAdmission.enabled": "写入判定(只记不拦)",
|
|
1045
|
+
"memory.features.writeAdmission.enabled.hint": "写入前判空白与噪声,先记账不拦截。",
|
|
1046
|
+
"memory.features.writeAdmission.enforce": "写入拦截",
|
|
1047
|
+
"memory.features.writeAdmission.enforce.hint": "判为噪声的写入直接拒绝。需先开「写入判定」。",
|
|
1048
|
+
"memory.features.preInjectGate.enabled": "注入判定(只记不拦)",
|
|
1049
|
+
"memory.features.preInjectGate.enabled.hint": "注入前判出意见类记忆,先记账观察。",
|
|
1050
|
+
"memory.features.preInjectGate.enforce": "注入过滤",
|
|
1051
|
+
"memory.features.preInjectGate.enforce.hint": "判定不合适的候选注入时丢掉。需先开「注入判定」。",
|
|
980
1052
|
// #164 A2:密钥/PII 判据自己的闸。文案要写清它管的是哪一批判据(与上面的
|
|
981
1053
|
// 空白/噪声分开)、它跑在「写入准入判定」里面(那一项关着就不跑),以及
|
|
982
1054
|
// 「开着也不拦」——拦是上面那项的事。
|
|
983
|
-
"memory.features.sensitiveScanEnabled": "
|
|
984
|
-
"memory.features.sensitiveScanEnabled.hint": "
|
|
985
|
-
"memory.features.bm25SearchEnabled": "
|
|
986
|
-
"memory.features.bm25SearchEnabled.hint": "
|
|
987
|
-
"memory.features.conflictFreezeEnabled": "
|
|
988
|
-
"memory.features.conflictFreezeEnabled.hint": "
|
|
989
|
-
"memory.features.
|
|
990
|
-
"memory.features.
|
|
991
|
-
"memory.features.
|
|
992
|
-
"memory.features.
|
|
993
|
-
"memory.features.
|
|
994
|
-
"memory.features.
|
|
995
|
-
"memory.features.
|
|
996
|
-
"memory.features.
|
|
1055
|
+
"memory.features.sensitiveScanEnabled": "密钥与隐私扫描",
|
|
1056
|
+
"memory.features.sensitiveScanEnabled.hint": "写入判定里顺带扫密钥/个人信息,命中只记账。",
|
|
1057
|
+
"memory.features.bm25SearchEnabled": "BM25 检索",
|
|
1058
|
+
"memory.features.bm25SearchEnabled.hint": "关键词一路的词法检索,与语义互补。",
|
|
1059
|
+
"memory.features.conflictFreezeEnabled": "矛盾冻结",
|
|
1060
|
+
"memory.features.conflictFreezeEnabled.hint": "整理发现矛盾先冻结待确认,不自动裁决。",
|
|
1061
|
+
"memory.features.conflictLedgerMode": "矛盾处理档位",
|
|
1062
|
+
"memory.features.conflictLedgerMode.hint": "off:矛盾走人审队列(现状)。shadow:分诊只记账零处置,先跑一周看分布。enforce:确定性处置(近重复自动归档、演进落取代链、矛盾注入标注),人审与 LLM 裁决退出。与「矛盾冻结」互斥,同开时冻结让位。",
|
|
1063
|
+
"memory.features.conflictLedgerMode.off": "off(人审队列)",
|
|
1064
|
+
"memory.features.conflictLedgerMode.shadow": "shadow(只记账)",
|
|
1065
|
+
"memory.features.conflictLedgerMode.enforce": "enforce(确定性处置)",
|
|
1066
|
+
"memory.features.scopeEnabled": "来源标注",
|
|
1067
|
+
"memory.features.scopeEnabled.hint": "每条记忆标上来自哪个 Agent / 工作区。",
|
|
1068
|
+
"memory.features.strictScope": "严格隔离",
|
|
1069
|
+
"memory.features.strictScope.hint": "别的会话标私有的记忆彻底不可见。需先开「来源标注」。",
|
|
1070
|
+
"memory.features.trustEpistemicWeighting": "可信度加权",
|
|
1071
|
+
"memory.features.trustEpistemicWeighting.hint": "来源可信度高的记忆排前面(实验性)。",
|
|
1072
|
+
"memory.features.reflectionFailureTracking": "失败案例留存",
|
|
1073
|
+
"memory.features.reflectionFailureTracking.hint": "整理失败的原因与案例记下来供诊断。",
|
|
997
1074
|
"memory.features.embedRoute": "语义检索路线",
|
|
998
1075
|
"memory.features.embedProvider": "Embedding 提供方",
|
|
999
1076
|
"memory.features.embedProvider.openai": "OpenAI 兼容接口",
|
|
@@ -1006,23 +1083,23 @@ window.__ModuleLoader__.load({
|
|
|
1006
1083
|
"memory.features.ollamaBaseUrl": "Ollama 服务地址",
|
|
1007
1084
|
"memory.features.ollamaModel": "Ollama 模型名",
|
|
1008
1085
|
"memory.features.dreamProvider": "巩固模型 Provider",
|
|
1009
|
-
"memory.features.dreamModel": "
|
|
1010
|
-
"memory.features.dreamModelHint": "留空 =
|
|
1011
|
-
"memory.features.dreamPeakHours": "
|
|
1012
|
-
"memory.features.dreamPeakHours.hint": "
|
|
1086
|
+
"memory.features.dreamModel": "巩固模型名",
|
|
1087
|
+
"memory.features.dreamModelHint": "留空 = 跟随主对话模型;只影响巩固用的模型。",
|
|
1088
|
+
"memory.features.dreamPeakHours": "巩固高峰时段",
|
|
1089
|
+
"memory.features.dreamPeakHours.hint": "巩固避开这些时段、事后补跑。写法 09:00-18:00,可多段。",
|
|
1013
1090
|
"memory.features.dreamPeakHours.invalid": "当前值无法解析为时段,将按未配置处理(巩固全天候生效)。写法:09:00-18:00,多段用逗号分隔,可带星期前缀(mon-fri 08:00-12:00),支持跨零点;24:00 越界请写 00:00",
|
|
1014
|
-
"memory.features.summarizePeakHours": "
|
|
1015
|
-
"memory.features.summarizePeakHours.hint": "
|
|
1091
|
+
"memory.features.summarizePeakHours": "蒸馏高峰时段",
|
|
1092
|
+
"memory.features.summarizePeakHours.hint": "蒸馏避开这些时段,窗口攒着非高峰一次蒸完。",
|
|
1016
1093
|
"memory.features.summarizePeakHours.invalid": "当前值无法解析为时段,将按未配置处理(蒸馏全天候生效)。写法与巩固侧相同:09:00-18:00,多段用逗号分隔,可带星期前缀,支持跨零点",
|
|
1017
1094
|
"memory.features.sleepProvider": "睡眠 Provider",
|
|
1018
1095
|
"memory.features.sleepModel": "睡眠模型",
|
|
1019
1096
|
"memory.features.sleepModelHint": "留空 = 用巩固模型或当前模型;建议选非思考模型",
|
|
1020
1097
|
// Issue #478:max_tokens 预算键。hint 写清两件事:这是「单轮输出预算」,
|
|
1021
1098
|
// 以及服务商硬上限更小时后端会按报错自动降级重试(对齐 #478 的后端行为)。
|
|
1022
|
-
"memory.features.dreamMaxTokens": "
|
|
1023
|
-
"memory.features.dreamMaxTokens.hint": "
|
|
1024
|
-
"memory.features.sleepMaxTokens": "
|
|
1025
|
-
"memory.features.sleepMaxTokens.hint": "
|
|
1099
|
+
"memory.features.dreamMaxTokens": "巩固输出上限",
|
|
1100
|
+
"memory.features.dreamMaxTokens.hint": "单轮巩固最多生成的 token 数,超上限自动降级。",
|
|
1101
|
+
"memory.features.sleepMaxTokens": "睡眠输出上限",
|
|
1102
|
+
"memory.features.sleepMaxTokens.hint": "睡眠阶段单轮输出预算,超上限自动降级。",
|
|
1026
1103
|
"memory.features.routeFollowDefault": "跟随默认路由",
|
|
1027
1104
|
"memory.features.modelTest": "测试连通性",
|
|
1028
1105
|
"memory.features.modelTesting": "测试中…",
|
|
@@ -1204,6 +1281,15 @@ window.__ModuleLoader__.load({
|
|
|
1204
1281
|
"memory.status.conflictQueue.markReviewed": "仅标记已处理",
|
|
1205
1282
|
"memory.status.conflictQueue.applyHint": "确认后:保留方正文追加已否决注记,另一方归档。",
|
|
1206
1283
|
"memory.status.conflictQueue.missing": "(该记忆已不存在)",
|
|
1284
|
+
"memory.status.ledger.title": "矛盾账本(分诊 · 只读)",
|
|
1285
|
+
"memory.status.ledger.dist": "分诊分布:",
|
|
1286
|
+
"memory.status.ledger.total": "累计",
|
|
1287
|
+
"memory.status.ledger.status": "状态",
|
|
1288
|
+
"memory.status.ledger.loadError": "账本加载失败",
|
|
1289
|
+
"memory.status.ledger.tier.dup": "近重复",
|
|
1290
|
+
"memory.status.ledger.tier.cross-scope-dup": "跨作用域",
|
|
1291
|
+
"memory.status.ledger.tier.evolution": "时序演进",
|
|
1292
|
+
"memory.status.ledger.tier.conflict": "真矛盾",
|
|
1207
1293
|
"memory.status.conflictQueue.goto": "查看待确认冲突队列",
|
|
1208
1294
|
"memory.status.conflictQueue.resolved": "已裁决:保留{name}",
|
|
1209
1295
|
"memory.status.conflictQueue.refresh": "刷新队列",
|
|
@@ -1364,6 +1450,7 @@ window.__ModuleLoader__.load({
|
|
|
1364
1450
|
"memory.status.token.conflictsFoot": "来自 /dream-status · 实时",
|
|
1365
1451
|
"memory.status.token.donut": "按组件 · 谁花了这些 token",
|
|
1366
1452
|
"memory.status.token.days": "按天 · 7 天趋势",
|
|
1453
|
+
"memory.status.token.daysEmpty": "最近 7 天没有 token 记录",
|
|
1367
1454
|
"memory.status.token.heat": "8 周热力 · 每天花了多少",
|
|
1368
1455
|
"memory.status.token.heatLess": "少",
|
|
1369
1456
|
"memory.status.token.heatMore": "多",
|
|
@@ -1422,6 +1509,36 @@ window.__ModuleLoader__.load({
|
|
|
1422
1509
|
"wiki.t1.failedCount": "failed",
|
|
1423
1510
|
"wiki.stale": "Code changed",
|
|
1424
1511
|
"wiki.stale.hint": "The repository's code changed since the last run, so these pages may be out of date — press \"Rebuild skeleton\" to refresh.",
|
|
1512
|
+
// Evidence review (Grounded Claims): which claims a change touched, without
|
|
1513
|
+
// re-running T1. "Could not compare" and "nothing touched" must read as two
|
|
1514
|
+
// different sentences — if they look the same on screen, an uncomputed check
|
|
1515
|
+
// passes for a clean bill of health, which is exactly what #607 was about.
|
|
1516
|
+
"wiki.drift": "Evidence check",
|
|
1517
|
+
"wiki.drift.hint": "Diffs each claim's recorded file and line range against git: only the ones a change actually touched are flagged. No model call.",
|
|
1518
|
+
"wiki.drift.loading": "Comparing…",
|
|
1519
|
+
"wiki.drift.summary": "{pages} pages · {claims} claims · {risk} need review",
|
|
1520
|
+
"wiki.drift.clean": "The baseline is the current HEAD: every claim still points where it did.",
|
|
1521
|
+
"wiki.drift.none": "This change did not touch any claim anchor.",
|
|
1522
|
+
"wiki.drift.gitUnreadable": "Cannot read git for this repository (not a git directory, or unreadable), so nothing can be compared.",
|
|
1523
|
+
"wiki.drift.anchorStale": "The baseline commit recorded at generation time no longer exists in git (force-push or a shallow clone cut it off): nothing to compare against — rebuild the skeleton.",
|
|
1524
|
+
"wiki.drift.blocked": "Comparison failed",
|
|
1525
|
+
"wiki.drift.touched": "A change touched this line",
|
|
1526
|
+
"wiki.drift.shifted": "Insertions above moved it {n} lines",
|
|
1527
|
+
"wiki.drift.deleted": "File deleted",
|
|
1528
|
+
"wiki.drift.unverifiable": "No line number recorded — cannot locate",
|
|
1529
|
+
"wiki.drift.inconsistent": "This page has prose in the file but no claims on record (the last T1 run did not finish), so its evidence cannot be checked.",
|
|
1530
|
+
// Claim provenance: each page's claims were read from one specific commit. Without
|
|
1531
|
+
// saying which, "evidence check" compares against an unknown point in time.
|
|
1532
|
+
"wiki.drift.verified": "Verified at {sha}",
|
|
1533
|
+
"wiki.drift.verifiedUnknown": "This page did not record which commit its claims were verified against (earlier enrichment did not store one), so staleness cannot be decided. Re-running T1 will record it.",
|
|
1534
|
+
"wiki.drift.verifiedApprox": "This page was enriched before provenance was recorded; the comparison below starts from the repository baseline {sha} (earlier than the real one, so it can over-report but never miss).",
|
|
1535
|
+
"wiki.drift.anchorStalePage": "The commit this page was verified against no longer exists in git (force-push or a shallow clone cut it off), so this page cannot be compared.",
|
|
1536
|
+
"wiki.drift.undecidable": "{n} page(s) have no usable verification baseline and were left out of the comparison",
|
|
1537
|
+
// Auto-refresh was deliberately skipped. This must read as "I did not do it,
|
|
1538
|
+
// because doing it would lose something", never as "refresh failed".
|
|
1539
|
+
"wiki.autoRefresh.skipped": "Auto-refresh was skipped: {n} page(s) in this repository are already enriched, and rebuilding the skeleton would wipe their prose and claims. Click Rebuild skeleton yourself if you want a new snapshot (it tells you which pages it drops).",
|
|
1540
|
+
"wiki.autoRefresh.skippedGeneric": "Auto-refresh was skipped: this repository already has enriched pages, and rebuilding the skeleton would wipe their prose and claims. Click Rebuild skeleton yourself if you want a new snapshot.",
|
|
1541
|
+
"wiki.autoRefresh.failed": "Auto-refresh was attempted but failed",
|
|
1425
1542
|
"wiki.run.pathRequired": "Enter the repository's absolute path first",
|
|
1426
1543
|
"wiki.run.failed": "Generate failed",
|
|
1427
1544
|
"wiki.run.notAbsolute": "Give an absolute path (for example /home/me/code/my-repo): relative paths resolve against the server process's working directory, so the same request can land somewhere else depending on how the host was started.",
|
|
@@ -1592,16 +1709,16 @@ window.__ModuleLoader__.load({
|
|
|
1592
1709
|
"memory.settings.factsRunStatusOk": "ok",
|
|
1593
1710
|
"memory.settings.factsRunStatusNoop": "no change",
|
|
1594
1711
|
"memory.settings.factsRunStatusFailed": "failed",
|
|
1595
|
-
"memory.features.personaEnabled": "Persona
|
|
1596
|
-
"memory.features.personaEnabled.hint": "
|
|
1597
|
-
"memory.features.personaStrictAddressing": "Strict
|
|
1598
|
-
"memory.features.personaStrictAddressing.hint": "
|
|
1599
|
-
"memory.features.profileExtractEnabled": "Profile draft
|
|
1600
|
-
"memory.features.profileExtractEnabled.hint": "
|
|
1712
|
+
"memory.features.personaEnabled": "Persona injection",
|
|
1713
|
+
"memory.features.personaEnabled.hint": "Injects persona cards each turn; the card switch must be on too.",
|
|
1714
|
+
"memory.features.personaStrictAddressing": "Strict addressing",
|
|
1715
|
+
"memory.features.personaStrictAddressing.hint": "No persona card means no injection; the default card covers unresolvable sessions only.",
|
|
1716
|
+
"memory.features.profileExtractEnabled": "Profile draft",
|
|
1717
|
+
"memory.features.profileExtractEnabled.hint": "Generates a draft profile from memories — copy only, never auto-applied.",
|
|
1601
1718
|
"memory.features.profileFactsEnabled": "Auto facts",
|
|
1602
|
-
"memory.features.profileFactsEnabled.hint": "
|
|
1603
|
-
"memory.features.chorographyEnabled": "Repo
|
|
1604
|
-
"memory.features.chorographyEnabled.hint": "Reads
|
|
1719
|
+
"memory.features.profileFactsEnabled.hint": "Maintains the facts list after every consolidation run.",
|
|
1720
|
+
"memory.features.chorographyEnabled": "Repo chorography",
|
|
1721
|
+
"memory.features.chorographyEnabled.hint": "Reads the repo into a paginated gazetteer; four entry points.",
|
|
1605
1722
|
"memory.features.chorographyInject": "Opening injection tier",
|
|
1606
1723
|
"memory.features.chorographyInject.hint": "How much of the atlas outline goes into context every turn: light lists the six modules with the most files (about 8 lines), full lists every module, off injects nothing (tools and the panel page still work). Only module name / file count / page status / page path are injected, never page bodies — the model fetches those with choro_read_page. The lightweight preset forces off.",
|
|
1607
1724
|
"memory.features.chorographyInject.off": "off (no injection)",
|
|
@@ -1612,7 +1729,7 @@ window.__ModuleLoader__.load({
|
|
|
1612
1729
|
"memory.features.chorographyT1FromPanel": "Allow T1 from panel",
|
|
1613
1730
|
"memory.features.chorographyT1FromPanel.hint": "Lets the \"Enrich prose (T1)\" button on the Repo Atlas panel start a headless session that fills in the semantic sections module by module. On by default: it is a button a human presses, not a background behaviour. With it off the button stays but points you at the CLI.",
|
|
1614
1731
|
"memory.features.kbEnabled": "Knowledge base",
|
|
1615
|
-
"memory.features.kbEnabled.hint": "Chunks and indexes external material
|
|
1732
|
+
"memory.features.kbEnabled.hint": "Chunks and indexes external material; adds a panel page.",
|
|
1616
1733
|
"memory.features.group.kb": "Knowledge base",
|
|
1617
1734
|
"memory.settings.rules": "Rules",
|
|
1618
1735
|
"memory.settings.rulesHint": "Rules the agent must follow — add or remove anytime, effective next turn.",
|
|
@@ -1635,6 +1752,7 @@ window.__ModuleLoader__.load({
|
|
|
1635
1752
|
"memory.explorer.tabSettings": "Settings",
|
|
1636
1753
|
"memory.explorer.tabStatus": "Status",
|
|
1637
1754
|
"memory.explorer.tabPersona": "Persona",
|
|
1755
|
+
"memory.explorer.tabAffective": "Affective",
|
|
1638
1756
|
"memory.entities.count": "{n} entities",
|
|
1639
1757
|
"memory.entities.countSampled": "The list shows the newest {shown} only; the total above is the whole library",
|
|
1640
1758
|
"memory.entities.pick": "Select an entity to see its attributes and relations",
|
|
@@ -1682,6 +1800,18 @@ window.__ModuleLoader__.load({
|
|
|
1682
1800
|
"memory.time.minutes": "{n}m ago",
|
|
1683
1801
|
"memory.time.hours": "{n}h ago",
|
|
1684
1802
|
"memory.time.days": "{n}d ago",
|
|
1803
|
+
"memory.affective.title": "Affective attributions",
|
|
1804
|
+
"memory.affective.sub": "Grouped by who · which emotion · toward what; lasting tendencies are listed apart from single occasions.",
|
|
1805
|
+
"memory.affective.loading": "Loading…",
|
|
1806
|
+
"memory.affective.error": "Could not load — that is not the same as having nothing recorded; this request failed.",
|
|
1807
|
+
"memory.affective.empty": "No affective edges yet. Turn on the affective phase and let one nightly pass run.",
|
|
1808
|
+
"memory.affective.occasion": "Single occasion",
|
|
1809
|
+
"memory.affective.trait": "Lasting tendency",
|
|
1810
|
+
"memory.affective.sessions": "across {n} independent sessions",
|
|
1811
|
+
"memory.affective.evidence": "{n} pieces of evidence",
|
|
1812
|
+
"memory.affective.lastSeen": "last {time}",
|
|
1813
|
+
"memory.affective.truncated": "Showing the newest {n} edges only; older ones are not on this page",
|
|
1814
|
+
"memory.affective.skipped": "{n} edges carried no emotion name and were left out",
|
|
1685
1815
|
"memory.graph.title": "Memory Graph",
|
|
1686
1816
|
"memory.graph.aria": "Memory graph",
|
|
1687
1817
|
"memory.graph.placeholder": "Type an entity name to see its network…",
|
|
@@ -1714,6 +1844,12 @@ window.__ModuleLoader__.load({
|
|
|
1714
1844
|
"memory.graph.src.confirmed": "confirmed",
|
|
1715
1845
|
"memory.graph.src.tag": "tag overlap",
|
|
1716
1846
|
"memory.graph.src.llm": "model-extracted",
|
|
1847
|
+
"memory.graph.emotion": "emotion {e}",
|
|
1848
|
+
"memory.graph.affectiveOccasion": "this occasion",
|
|
1849
|
+
"memory.graph.affectiveOccasions": "{n} occasions so far",
|
|
1850
|
+
"memory.graph.affectiveTrait": "lasting tendency · across {n} independent occasions",
|
|
1851
|
+
"memory.graph.affectiveEvidence": "{n} pieces of evidence",
|
|
1852
|
+
"memory.graph.affectiveLegacy": "older affective edge (no occasion record)",
|
|
1717
1853
|
"memory.settings.vectorTitle": "Vector Search",
|
|
1718
1854
|
"memory.settings.vectorHint": "Match by meaning, not just wording — connect any OpenAI-compatible embeddings API.",
|
|
1719
1855
|
"memory.settings.vectorEnabled": "Enable vector search",
|
|
@@ -1804,39 +1940,48 @@ window.__ModuleLoader__.load({
|
|
|
1804
1940
|
// group.basic is the first-screen tier; the scope group sits there too so
|
|
1805
1941
|
// "can other sessions see this?" is answerable without opening anything.
|
|
1806
1942
|
"memory.features.group.basic": "Common (start here)",
|
|
1807
|
-
|
|
1943
|
+
// group.ext: mneme proper is memory only; chorography, the knowledge base
|
|
1944
|
+
// and the diary are three optional subsystems. Each switch decides whether
|
|
1945
|
+
// the subsystem exists at all, not how much it costs per turn — mixing them
|
|
1946
|
+
// into the behaviour groups is why "you can only find it once it is already
|
|
1947
|
+
// on" (#607's "the knowledge base never took shape" was half an entry-point
|
|
1948
|
+
// problem), so they get their own first-screen group.
|
|
1949
|
+
"memory.features.group.ext": "Extensions",
|
|
1950
|
+
"memory.features.group.safety": "Guardrails & audit",
|
|
1951
|
+
"memory.features.group.pipeline": "Distillation & writing",
|
|
1952
|
+
"memory.features.group.retrieve": "Retrieval & injection",
|
|
1953
|
+
"memory.features.group.persona": "Personalization",
|
|
1808
1954
|
"memory.features.group.mo": "Its workflow (M.O.)",
|
|
1809
|
-
"memory.features.group.
|
|
1810
|
-
"memory.features.group.
|
|
1811
|
-
"memory.features.group.scope": "Between sessions",
|
|
1955
|
+
"memory.features.group.dream": "Consolidation & sleep",
|
|
1956
|
+
"memory.features.group.scope": "Session isolation",
|
|
1812
1957
|
"memory.features.group.advanced": "Advanced",
|
|
1813
1958
|
"memory.features.allToggle": "All settings",
|
|
1814
|
-
"memory.features.advancedToggle": "
|
|
1959
|
+
"memory.features.advancedToggle": "9 rarer switches",
|
|
1815
1960
|
"memory.features.restartHint": "Takes effect after restarting DSH",
|
|
1816
1961
|
"memory.features.loadFailed": "Failed to load",
|
|
1817
1962
|
"memory.features.saveFailed": "Failed to save",
|
|
1818
|
-
"memory.features.autoInject": "
|
|
1819
|
-
"memory.features.autoInject.hint": "
|
|
1820
|
-
"memory.features.injectGuidanceEnabled": "
|
|
1821
|
-
"memory.features.injectGuidanceEnabled.hint": "
|
|
1822
|
-
"memory.features.continuityRescueEnabled": "
|
|
1823
|
-
"memory.features.continuityRescueEnabled.hint": "
|
|
1963
|
+
"memory.features.autoInject": "Auto-inject memories",
|
|
1964
|
+
"memory.features.autoInject.hint": "Relevant memories reach the agent every turn — no need to ask.",
|
|
1965
|
+
"memory.features.injectGuidanceEnabled": "Teach memory usage",
|
|
1966
|
+
"memory.features.injectGuidanceEnabled.hint": "Tool descriptions explain when to search vs. write.",
|
|
1967
|
+
"memory.features.continuityRescueEnabled": "Pre-compaction snapshot",
|
|
1968
|
+
"memory.features.continuityRescueEnabled.hint": "Saves key context before compaction eats it.",
|
|
1824
1969
|
"memory.features.parentOff": "Inactive while the switch above is off",
|
|
1825
|
-
"memory.features.tagDefault": "default",
|
|
1826
|
-
"memory.features.tagDefault.hint": "This one is at its factory default — you have not changed it",
|
|
1827
1970
|
"memory.features.tagChanged": "changed",
|
|
1828
1971
|
"memory.features.tagChanged.hint": "You changed this one; it no longer uses the default",
|
|
1829
1972
|
"memory.features.tagPending": "restart pending",
|
|
1830
1973
|
"memory.features.tagPending.hint": "Saved, but not yet in effect for this session — restart DSH (or toggle this plugin off and on in the plugin manager) to apply it",
|
|
1831
1974
|
"memory.features.pendingRestartNotice": "Some changes are not in effect yet: switches marked \"restart pending\" apply only after you restart DSH (toggling this plugin off and on in the plugin manager works too).",
|
|
1832
|
-
"memory.features.autoSummarize": "
|
|
1833
|
-
"memory.features.autoSummarize.hint": "
|
|
1834
|
-
"memory.features.hotMemoryEnabled": "
|
|
1835
|
-
"memory.features.hotMemoryEnabled.hint": "
|
|
1836
|
-
"memory.features.
|
|
1837
|
-
"memory.features.
|
|
1838
|
-
"memory.features.
|
|
1839
|
-
"memory.features.
|
|
1975
|
+
"memory.features.autoSummarize": "Auto-distill conversations",
|
|
1976
|
+
"memory.features.autoSummarize.hint": "Conversations are distilled into long-term memories automatically.",
|
|
1977
|
+
"memory.features.hotMemoryEnabled": "Recent turns verbatim",
|
|
1978
|
+
"memory.features.hotMemoryEnabled.hint": "Injects the last few raw turns; never written to long-term memory.",
|
|
1979
|
+
"memory.features.injectRotationTurns": "Rotate memories: ones used in recent turns rank lower",
|
|
1980
|
+
"memory.features.injectRotationTurns.hint": "0–20: memories injected in the last N turns lose priority so lower-ranked ones get a turn; 0 = off (the same top batch repeats every turn, default). The #637 duplicate-injection flood mostly came from hot-memory snapshot churn (fixed); this window addresses the other half: the same batch holding the slot forever",
|
|
1981
|
+
"memory.features.injectTimePrefix": "Inject date & weekday",
|
|
1982
|
+
"memory.features.injectTimePrefix.hint": "One date line at session start — 'today' gets reliable.",
|
|
1983
|
+
"memory.features.entityExtractionEnabled": "Entity extraction",
|
|
1984
|
+
"memory.features.entityExtractionEnabled.hint": "Pulls people/projects/concepts from each memory; grows a graph.",
|
|
1840
1985
|
"memory.features.entityExtractionProvider": "Entity extraction provider",
|
|
1841
1986
|
"memory.features.entityExtractionModel": "Entity extraction model",
|
|
1842
1987
|
"memory.features.entityExtractionReasoning": "Entity extraction reasoning",
|
|
@@ -1852,14 +1997,16 @@ window.__ModuleLoader__.load({
|
|
|
1852
1997
|
"memory.features.summarizeReasoningEffort.high": "High",
|
|
1853
1998
|
"memory.features.summarizeReasoningEffort.hint": "For thinking models, prefer Low or No reasoning so thinking cannot fill the output budget and break summaries",
|
|
1854
1999
|
"memory.features.entityExtractionModelHint": "Provider / model empty = follow the main conversation model; reasoning \"Follow default\" = provider default",
|
|
1855
|
-
"memory.features.codingRetrospect": "
|
|
1856
|
-
"memory.features.codingRetrospect.hint": "
|
|
1857
|
-
"memory.features.rerankEnabled": "
|
|
1858
|
-
"memory.features.rerankEnabled.hint": "
|
|
1859
|
-
"memory.features.heatEnabled": "
|
|
1860
|
-
"memory.features.heatEnabled.hint": "
|
|
1861
|
-
"memory.features.
|
|
1862
|
-
"memory.features.
|
|
2000
|
+
"memory.features.codingRetrospect": "Coding retrospection",
|
|
2001
|
+
"memory.features.codingRetrospect.hint": "Reviews whole coding sessions: pitfalls, constraints, rejected plans.",
|
|
2002
|
+
"memory.features.rerankEnabled": "Reranking",
|
|
2003
|
+
"memory.features.rerankEnabled.hint": "A local reranker re-scores recall — more accurate, slower.",
|
|
2004
|
+
"memory.features.heatEnabled": "Heat decay",
|
|
2005
|
+
"memory.features.heatEnabled.hint": "Stale memories rank lower, like a forgetting curve.",
|
|
2006
|
+
"memory.features.injectHeatEnabled": "Heat-aware ranking",
|
|
2007
|
+
"memory.features.injectHeatEnabled.hint": "Injection ordering weighs memory heat; requires heat decay.",
|
|
2008
|
+
"memory.features.documentMemoryEnabled": "Docs as summary cards",
|
|
2009
|
+
"memory.features.documentMemoryEnabled.hint": "Long documents are stored as summary cards; bodies on demand.",
|
|
1863
2010
|
// --- v0.9.0 M.O. / Modus Operandi (beta) ---
|
|
1864
2011
|
"memory.actions.beta": "Beta",
|
|
1865
2012
|
"memory.actions.notice": "Extracted steps are candidates: they may be wrong, outdated, or refuted.",
|
|
@@ -1931,10 +2078,10 @@ window.__ModuleLoader__.load({
|
|
|
1931
2078
|
"memory.features.actionPipelineEnabled.hint": "Read-only view: the ways of working that actually got walked through in real sessions, drawn as a graph — session-header icon, full sidebar view, and a per-run settlement card. Viewing is not writing: distillation has its own switch below, and run accounting has its own (/mo-run). Modus Operandi. Takes effect after restarting DSH",
|
|
1932
2079
|
"memory.features.actionNudgeEnabled": "Auto-distill workflows (beta)",
|
|
1933
2080
|
"memory.features.actionNudgeEnabled.hint": "On: after each turn, look at this project's own session logs and mine the actions it keeps repeating into a proposal in the memory store — it still needs your sign-off before becoming a workflow. Drafts only; never auto-activates; never writes files into the repo. Hooks a hot path and decompresses logs, so light mode forces it off. Takes effect after restarting DSH",
|
|
1934
|
-
"memory.features.diaryEnabled": "Diary
|
|
1935
|
-
"memory.features.diaryEnabled.hint": "
|
|
1936
|
-
"memory.features.diaryWriteEveryMinutes": "Auto-write
|
|
1937
|
-
"memory.features.diaryWriteEveryMinutes.hint": "
|
|
2081
|
+
"memory.features.diaryEnabled": "Diary",
|
|
2082
|
+
"memory.features.diaryEnabled.hint": "Persona-carrying agents write one diary a day from real conversations.",
|
|
2083
|
+
"memory.features.diaryWriteEveryMinutes": "Auto-write interval (min)",
|
|
2084
|
+
"memory.features.diaryWriteEveryMinutes.hint": "Wakes to check if today's diary is due; 0 = chat end/restart only.",
|
|
1938
2085
|
"memory.diary.title": "Diary",
|
|
1939
2086
|
"memory.diary.note": "Written from what actually happened in this project: no conversation, no entry, nothing invented.",
|
|
1940
2087
|
"memory.diary.write": "Write today",
|
|
@@ -1958,8 +2105,7 @@ window.__ModuleLoader__.load({
|
|
|
1958
2105
|
"memory.diary.bookLabel": "Paper and type",
|
|
1959
2106
|
"memory.diary.book.plain": "Plain",
|
|
1960
2107
|
"memory.diary.book.ruled": "Ruled",
|
|
1961
|
-
"memory.diary.book.grid": "
|
|
1962
|
-
"memory.diary.book.kraft": "Kraft",
|
|
2108
|
+
"memory.diary.book.grid": "Dots",
|
|
1963
2109
|
"memory.diary.count": "{n} entries",
|
|
1964
2110
|
"memory.diary.writtenOk": "Written: {path}",
|
|
1965
2111
|
"memory.diary.exportOk": "Saved to {path}",
|
|
@@ -1973,71 +2119,80 @@ window.__ModuleLoader__.load({
|
|
|
1973
2119
|
"memory.diary.status.written": "written",
|
|
1974
2120
|
"memory.diary.status.skipped": "skipped",
|
|
1975
2121
|
"memory.diary.status.failed": "failed",
|
|
1976
|
-
"memory.features.resilientModelDownload": "
|
|
1977
|
-
"memory.features.resilientModelDownload.hint": "
|
|
1978
|
-
"memory.features.searchSemanticDedup": "
|
|
1979
|
-
"memory.features.searchSemanticDedup.hint": "
|
|
1980
|
-
"memory.features.autoDream": "
|
|
1981
|
-
"memory.features.autoDream.hint": "
|
|
1982
|
-
"memory.features.sleepModeEnabled": "
|
|
1983
|
-
"memory.features.sleepModeEnabled.hint": "
|
|
2122
|
+
"memory.features.resilientModelDownload": "Resumable downloads",
|
|
2123
|
+
"memory.features.resilientModelDownload.hint": "Model downloads resume from breakpoints and retry.",
|
|
2124
|
+
"memory.features.searchSemanticDedup": "Semantic dedup",
|
|
2125
|
+
"memory.features.searchSemanticDedup.hint": "Near-duplicate recall results collapse into one.",
|
|
2126
|
+
"memory.features.autoDream": "Background consolidation",
|
|
2127
|
+
"memory.features.autoDream.hint": "Consolidates (dedupe, merge, archive) automatically when idle.",
|
|
2128
|
+
"memory.features.sleepModeEnabled": "Sleep cleanup",
|
|
2129
|
+
"memory.features.sleepModeEnabled.hint": "Tiered compaction and tidying while idle.",
|
|
1984
2130
|
// #619: affective attribution is a sub-phase of sleep — it cannot run while
|
|
1985
2131
|
// the parent is off, so it sits next to it.
|
|
1986
|
-
"memory.features.sleepAffectiveEnabled": "
|
|
1987
|
-
"memory.features.sleepAffectiveEnabled.hint": "
|
|
2132
|
+
"memory.features.sleepAffectiveEnabled": "Affect attribution",
|
|
2133
|
+
"memory.features.sleepAffectiveEnabled.hint": "Records who feels what about which matter, across repeated occasions.",
|
|
1988
2134
|
// #427 suggestion 5: hybridInject / selectiveInjectEnabled /
|
|
1989
2135
|
// adaptiveThresholdEnabled are three dials of one question — what comes
|
|
1990
2136
|
// back. Adjacent placement plus a hint that names the other two.
|
|
1991
|
-
"memory.features.hybridInject": "
|
|
1992
|
-
"memory.features.hybridInject.hint": "
|
|
1993
|
-
"memory.features.
|
|
1994
|
-
"memory.features.
|
|
1995
|
-
"memory.features.
|
|
1996
|
-
"memory.features.
|
|
1997
|
-
"memory.features.
|
|
1998
|
-
"memory.features.
|
|
2137
|
+
"memory.features.hybridInject": "Hybrid injection",
|
|
2138
|
+
"memory.features.hybridInject.hint": "Keyword + semantic retrieval merged for better recall.",
|
|
2139
|
+
"memory.features.entityRecallEnabled": "Entity recall",
|
|
2140
|
+
"memory.features.entityRecallEnabled.hint": "When the query matches an entity, its linked memories join the recall pool.",
|
|
2141
|
+
"memory.features.graphAnchoringEnabled": "Graph anchoring",
|
|
2142
|
+
"memory.features.graphAnchoringEnabled.hint": "Seeds on matched entities and cascades along relations into the recall pool.",
|
|
2143
|
+
"memory.features.selectiveInjectEnabled": "Selective injection",
|
|
2144
|
+
"memory.features.selectiveInjectEnabled.hint": "Filters by relevance before injecting — cleaner context.",
|
|
2145
|
+
"memory.features.adaptiveThresholdEnabled": "Adaptive threshold",
|
|
2146
|
+
"memory.features.adaptiveThresholdEnabled.hint": "Loosens the bar when recall is poor, tightens when good.",
|
|
2147
|
+
"memory.features.reflectionUpdateEnabled": "Allow rewrites",
|
|
2148
|
+
"memory.features.reflectionUpdateEnabled.hint": "Consolidation may rewrite and merge existing entries.",
|
|
1999
2149
|
"memory.features.distill": "Distillation & rate limiting",
|
|
2000
2150
|
"memory.features.distillRateLimitIntervalMs": "Distill spacing (ms)",
|
|
2001
2151
|
"memory.features.distillRateLimitRetries": "429 retries",
|
|
2002
2152
|
"memory.features.distillRateLimitBaseDelayMs": "Backoff base delay (ms)",
|
|
2003
2153
|
"memory.features.distillMaxChars": "Transcript cap (chars)",
|
|
2004
|
-
"memory.features.distillRecords.enabled": "Keep
|
|
2005
|
-
"memory.features.distillRecords.enabled.hint": "
|
|
2006
|
-
"memory.features.distillRecords.retentionDays": "
|
|
2007
|
-
"memory.features.distillRecords.retentionDays.hint": "1–3650 days; startup pruning only.
|
|
2154
|
+
"memory.features.distillRecords.enabled": "Keep distill sources",
|
|
2155
|
+
"memory.features.distillRecords.enabled.hint": "Persists transcript windows and per-memory provenance.",
|
|
2156
|
+
"memory.features.distillRecords.retentionDays": "Source retention (days)",
|
|
2157
|
+
"memory.features.distillRecords.retentionDays.hint": "1–3650 days; startup pruning only.",
|
|
2008
2158
|
"memory.features.codingBoostFactor": "Coding boost factor",
|
|
2009
|
-
"memory.features.memoryQualityFilter.enabled": "
|
|
2010
|
-
"memory.features.memoryQualityFilter.enabled.hint": "
|
|
2011
|
-
"memory.features.llmAudit.enabled": "
|
|
2012
|
-
"memory.features.llmAudit.enabled.hint": "
|
|
2159
|
+
"memory.features.memoryQualityFilter.enabled": "Quality filter",
|
|
2160
|
+
"memory.features.memoryQualityFilter.enabled.hint": "Low-value memories get archived and down-ranked.",
|
|
2161
|
+
"memory.features.llmAudit.enabled": "Call audit",
|
|
2162
|
+
"memory.features.llmAudit.enabled.hint": "Background model calls and token spend are on record.",
|
|
2013
2163
|
// #254 — two layers: enabled runs the check (audit only), enforce actually
|
|
2014
2164
|
// blocks. The hints must keep that difference explicit.
|
|
2015
|
-
"memory.features.writeAdmission.enabled": "
|
|
2016
|
-
"memory.features.writeAdmission.enabled.hint": "
|
|
2017
|
-
"memory.features.writeAdmission.enforce": "
|
|
2018
|
-
"memory.features.writeAdmission.enforce.hint": "
|
|
2019
|
-
"memory.features.preInjectGate.enabled": "
|
|
2020
|
-
"memory.features.preInjectGate.enabled.hint": "
|
|
2021
|
-
"memory.features.preInjectGate.enforce": "
|
|
2022
|
-
"memory.features.preInjectGate.enforce.hint": "
|
|
2165
|
+
"memory.features.writeAdmission.enabled": "Write admission (log only)",
|
|
2166
|
+
"memory.features.writeAdmission.enabled.hint": "Judges blank/noise writes and records them — nothing blocked.",
|
|
2167
|
+
"memory.features.writeAdmission.enforce": "Write blocking",
|
|
2168
|
+
"memory.features.writeAdmission.enforce.hint": "Flagged writes are actually refused. Requires write admission.",
|
|
2169
|
+
"memory.features.preInjectGate.enabled": "Pre-inject gate (log only)",
|
|
2170
|
+
"memory.features.preInjectGate.enabled.hint": "Flags opinion-type memories before injection — observe first.",
|
|
2171
|
+
"memory.features.preInjectGate.enforce": "Injection filtering",
|
|
2172
|
+
"memory.features.preInjectGate.enforce.hint": "Flagged candidates are dropped at injection. Requires the gate.",
|
|
2023
2173
|
// #164 A2: its own gate for the secret / PII rules (separate from the
|
|
2024
2174
|
// blank/noise pair above), and it runs inside the write admission checks
|
|
2025
2175
|
// above, so with those off it never runs. Hits are audited only;
|
|
2026
2176
|
// enforcement stays with the key above.
|
|
2027
|
-
"memory.features.sensitiveScanEnabled": "
|
|
2028
|
-
"memory.features.sensitiveScanEnabled.hint": "
|
|
2029
|
-
"memory.features.bm25SearchEnabled": "
|
|
2030
|
-
"memory.features.bm25SearchEnabled.hint": "
|
|
2031
|
-
"memory.features.conflictFreezeEnabled": "
|
|
2032
|
-
"memory.features.conflictFreezeEnabled.hint": "
|
|
2033
|
-
"memory.features.
|
|
2034
|
-
"memory.features.
|
|
2035
|
-
"memory.features.
|
|
2036
|
-
"memory.features.
|
|
2037
|
-
"memory.features.
|
|
2038
|
-
"memory.features.
|
|
2039
|
-
"memory.features.
|
|
2040
|
-
"memory.features.
|
|
2177
|
+
"memory.features.sensitiveScanEnabled": "Secrets & PII scan",
|
|
2178
|
+
"memory.features.sensitiveScanEnabled.hint": "Scans for secrets/PII inside write admission; logs only.",
|
|
2179
|
+
"memory.features.bm25SearchEnabled": "BM25 search",
|
|
2180
|
+
"memory.features.bm25SearchEnabled.hint": "Lexical keyword retrieval alongside semantics.",
|
|
2181
|
+
"memory.features.conflictFreezeEnabled": "Conflict freeze",
|
|
2182
|
+
"memory.features.conflictFreezeEnabled.hint": "Freezes contradictions for review instead of auto-deciding.",
|
|
2183
|
+
"memory.features.conflictLedgerMode": "Conflict handling mode",
|
|
2184
|
+
"memory.features.conflictLedgerMode.hint": "off: conflicts go to the human review queue (status quo). shadow: triage only records — zero disposition; run a week and inspect the distribution first. enforce: deterministic resolution (near-dups auto-archived, evolution chained, conflict pairs annotated); human review and LLM adjudication step out. Mutually exclusive with conflict freeze — freeze yields when both are on.",
|
|
2185
|
+
"memory.features.conflictLedgerMode.off": "off (review queue)",
|
|
2186
|
+
"memory.features.conflictLedgerMode.shadow": "shadow (record only)",
|
|
2187
|
+
"memory.features.conflictLedgerMode.enforce": "enforce (deterministic)",
|
|
2188
|
+
"memory.features.scopeEnabled": "Scope tagging",
|
|
2189
|
+
"memory.features.scopeEnabled.hint": "Tags each memory with its agent/workspace origin.",
|
|
2190
|
+
"memory.features.strictScope": "Strict isolation",
|
|
2191
|
+
"memory.features.strictScope.hint": "Memories marked private elsewhere become invisible. Needs tagging.",
|
|
2192
|
+
"memory.features.trustEpistemicWeighting": "Trust weighting",
|
|
2193
|
+
"memory.features.trustEpistemicWeighting.hint": "Higher-trust sources rank first (experimental).",
|
|
2194
|
+
"memory.features.reflectionFailureTracking": "Failure cases",
|
|
2195
|
+
"memory.features.reflectionFailureTracking.hint": "Keeps failure causes and cases for diagnosis.",
|
|
2041
2196
|
"memory.features.embedRoute": "Semantic search route",
|
|
2042
2197
|
"memory.features.embedProvider": "Embedding provider",
|
|
2043
2198
|
"memory.features.embedProvider.openai": "OpenAI-compatible API",
|
|
@@ -2051,22 +2206,22 @@ window.__ModuleLoader__.load({
|
|
|
2051
2206
|
"memory.features.ollamaModel": "Ollama model",
|
|
2052
2207
|
"memory.features.dreamProvider": "Consolidation provider",
|
|
2053
2208
|
"memory.features.dreamModel": "Consolidation model",
|
|
2054
|
-
"memory.features.dreamModelHint": "
|
|
2055
|
-
"memory.features.dreamPeakHours": "
|
|
2056
|
-
"memory.features.dreamPeakHours.hint": "
|
|
2209
|
+
"memory.features.dreamModelHint": "Empty = follow the host model; only affects consolidation.",
|
|
2210
|
+
"memory.features.dreamPeakHours": "Consolidation peak hours",
|
|
2211
|
+
"memory.features.dreamPeakHours.hint": "Consolidation avoids these windows and catches up later. Format 09:00-18:00, comma-separated.",
|
|
2057
2212
|
"memory.features.dreamPeakHours.invalid": "Current value is not a valid time-window spec and will be treated as unset (consolidation runs around the clock). Syntax: 09:00-18:00, comma-separated windows, optional weekday prefix (mon-fri 08:00-12:00), midnight-crossing allowed; write 00:00 instead of 24:00",
|
|
2058
|
-
"memory.features.summarizePeakHours": "
|
|
2059
|
-
"memory.features.summarizePeakHours.hint": "
|
|
2213
|
+
"memory.features.summarizePeakHours": "Distill peak hours",
|
|
2214
|
+
"memory.features.summarizePeakHours.hint": "Distillation avoids these windows; buffers flush off-peak.",
|
|
2060
2215
|
"memory.features.summarizePeakHours.invalid": "Current value is not a valid time-window spec and will be treated as unset (distillation runs around the clock). Syntax as on the consolidation side: 09:00-18:00, comma-separated windows, optional weekday prefix, midnight-crossing allowed",
|
|
2061
2216
|
"memory.features.sleepProvider": "Sleep provider",
|
|
2062
2217
|
"memory.features.sleepModel": "Sleep model",
|
|
2063
2218
|
"memory.features.sleepModelHint": "Leave empty to reuse the consolidation model; a non-reasoning model is recommended",
|
|
2064
2219
|
// Issue #478: max_tokens budget keys. Hint states both the scope (single-run
|
|
2065
2220
|
// output budget) and the backend auto-downgrade on provider cap rejection.
|
|
2066
|
-
"memory.features.dreamMaxTokens": "Consolidation
|
|
2067
|
-
"memory.features.dreamMaxTokens.hint": "
|
|
2068
|
-
"memory.features.sleepMaxTokens": "Sleep
|
|
2069
|
-
"memory.features.sleepMaxTokens.hint": "
|
|
2221
|
+
"memory.features.dreamMaxTokens": "Consolidation max tokens",
|
|
2222
|
+
"memory.features.dreamMaxTokens.hint": "Per-run output budget; auto-degrades past provider limits.",
|
|
2223
|
+
"memory.features.sleepMaxTokens": "Sleep max tokens",
|
|
2224
|
+
"memory.features.sleepMaxTokens.hint": "Per-run output budget for sleep phases; auto-degrades.",
|
|
2070
2225
|
"memory.features.routeFollowDefault": "Follow default route",
|
|
2071
2226
|
"memory.features.modelTest": "Test connectivity",
|
|
2072
2227
|
"memory.features.modelTesting": "Testing…",
|
|
@@ -2248,6 +2403,15 @@ window.__ModuleLoader__.load({
|
|
|
2248
2403
|
"memory.status.conflictQueue.markReviewed": "Mark reviewed only",
|
|
2249
2404
|
"memory.status.conflictQueue.applyHint": "On confirm: a veto note is appended to the kept side and the other side is archived.",
|
|
2250
2405
|
"memory.status.conflictQueue.missing": "(memory no longer exists)",
|
|
2406
|
+
"memory.status.ledger.title": "Conflict ledger (triage · read-only)",
|
|
2407
|
+
"memory.status.ledger.dist": "Triage distribution:",
|
|
2408
|
+
"memory.status.ledger.total": "total",
|
|
2409
|
+
"memory.status.ledger.status": "status",
|
|
2410
|
+
"memory.status.ledger.loadError": "Ledger failed to load",
|
|
2411
|
+
"memory.status.ledger.tier.dup": "Near-dup",
|
|
2412
|
+
"memory.status.ledger.tier.cross-scope-dup": "Cross-scope",
|
|
2413
|
+
"memory.status.ledger.tier.evolution": "Evolution",
|
|
2414
|
+
"memory.status.ledger.tier.conflict": "Conflict",
|
|
2251
2415
|
"memory.status.conflictQueue.goto": "View pending conflict queue",
|
|
2252
2416
|
"memory.status.conflictQueue.resolved": "Resolved: kept {name}",
|
|
2253
2417
|
"memory.status.conflictQueue.refresh": "Refresh queue",
|
|
@@ -2406,6 +2570,7 @@ window.__ModuleLoader__.load({
|
|
|
2406
2570
|
"memory.status.token.conflictsFoot": "From /dream-status · live",
|
|
2407
2571
|
"memory.status.token.donut": "By component · who spent these tokens",
|
|
2408
2572
|
"memory.status.token.days": "By day · 7-day trend",
|
|
2573
|
+
"memory.status.token.daysEmpty": "no token usage in the last 7 days",
|
|
2409
2574
|
"memory.status.token.heat": "8-week heat · daily spend",
|
|
2410
2575
|
"memory.status.token.heatLess": "less",
|
|
2411
2576
|
"memory.status.token.heatMore": "more",
|
|
@@ -2514,22 +2679,73 @@ window.__ModuleLoader__.load({
|
|
|
2514
2679
|
"--mneme-danger:color-mix(in srgb,var(--dsw-alias-state-error-primary) 80%,var(--dsw-alias-label-primary));" +
|
|
2515
2680
|
"--mneme-warn:color-mix(in srgb,var(--dsw-static-amber-600,#dd8629) 65%,var(--dsw-alias-label-primary));" +
|
|
2516
2681
|
"--mneme-ok:color-mix(in srgb,var(--dsw-static-green-500,#22c55e) 65%,var(--dsw-alias-label-primary));" +
|
|
2517
|
-
//
|
|
2518
|
-
//
|
|
2519
|
-
//
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
//
|
|
2682
|
+
// 图表六色(2026-11 重调,代号 E):每个槽两步混色。第一步把静态色与中性灰
|
|
2683
|
+
// 混掉一部分彩度(62%,紫用 74% 才不至于读成灰),第二步按各槽自己的比例混
|
|
2684
|
+
// label——label 随主题反向,深浅两套色阶因此自动成立,不用写第二份深色值。
|
|
2685
|
+
// 旧口径的四个毛病都是「各槽自己调、没有共同尺度」:彩度 0.065–0.172 差两倍
|
|
2686
|
+
// (红最刺眼)、chart-2 与 chart-5 都是绿(深色下 H 只差 4,分不出)、amber-600
|
|
2687
|
+
// 再压 40% 黑读成土褐、channel-5 的 L* 0.633 而 chart-4 只有 0.449(一条浮一条沉)。
|
|
2688
|
+
// 五色相固定为 blue / green / amber / red / violet,灰槽(chart-6)留给「其他」。
|
|
2689
|
+
// **刻意用 --dsw-static-* 而不是 --dsw-alias-state-***:别名是宿主状态的语义色,
|
|
2690
|
+
// 宿主调一次状态色就会连带换掉图表色盘,而图表色盘该是设计决定。
|
|
2691
|
+
// 实测浅色 L* 0.47–0.51、深色 0.72–0.81,彩度 0.065–0.095,最低对比 5.6:1(图形只要 3:1)。
|
|
2692
|
+
"--mneme-chart-1:color-mix(in srgb,color-mix(in srgb,var(--dsw-static-blue-500,#3b82f6) 62%,var(--dsw-static-neutral-500,#7f8287)) 70%,var(--dsw-alias-label-primary));" +
|
|
2693
|
+
"--mneme-chart-2:color-mix(in srgb,color-mix(in srgb,var(--dsw-static-green-500,#22c55e) 62%,var(--dsw-static-neutral-500,#7f8287)) 64%,var(--dsw-alias-label-primary));" +
|
|
2694
|
+
"--mneme-chart-3:color-mix(in srgb,color-mix(in srgb,var(--dsw-static-amber-500,#f59e0b) 62%,var(--dsw-static-neutral-500,#7f8287)) 74%,var(--dsw-alias-label-primary));" +
|
|
2695
|
+
"--mneme-chart-4:color-mix(in srgb,color-mix(in srgb,var(--dsw-static-red-500,#ef4444) 62%,var(--dsw-static-neutral-500,#7f8287)) 67%,var(--dsw-alias-label-primary));" +
|
|
2696
|
+
"--mneme-chart-5:color-mix(in srgb,color-mix(in srgb,var(--dsw-static-violet-400,#a78bfa) 74%,var(--dsw-static-neutral-500,#7f8287)) 62%,var(--dsw-alias-label-primary));" +
|
|
2697
|
+
// 第 6 槽是半透明灰(「其他」那一类)。它曾同时以「令牌 + 弧 + 图例点」三份
|
|
2698
|
+
// 内联副本存在过,改一处漏两处;现在弧与点都直接 var 引用这一份。
|
|
2526
2699
|
"--mneme-chart-6:color-mix(in srgb,var(--dsw-alias-label-primary) 50%,transparent);" +
|
|
2700
|
+
// 沉淀图(8 周「进来 vs 稳住」)与 donut(token 都去哪了)是两套分类,原先共用
|
|
2701
|
+
// chart-3 / chart-2:同一页里那根琥珀柱在 donut 是「实体抽取」、在沉淀图是「新增」,
|
|
2702
|
+
// 同色两义。这里给沉淀图独立语义名,取值与今天的 chart-3 / chart-2 逐字相同
|
|
2703
|
+
// (只换语义,视觉零变化),但**刻意不写 var(--mneme-chart-N)**——那是继承,
|
|
2704
|
+
// 以后调 donut 的六色盘会顺手改掉「库在膨胀还是稳住了」这张结论图的颜色。
|
|
2705
|
+
// 取色与 chart-3 同一套配方,但**逐字重写一份、不写 var 引用**:视觉跟着 donut 走,
|
|
2706
|
+
// 归属不跟着走(以后单独调沉淀图不必先改 donut)。
|
|
2707
|
+
"--mneme-sink-added:color-mix(in srgb,color-mix(in srgb,var(--dsw-static-amber-500,#f59e0b) 62%,var(--dsw-static-neutral-500,#7f8287)) 74%,var(--dsw-alias-label-primary));" +
|
|
2708
|
+
"--mneme-sink-merged:color-mix(in srgb,color-mix(in srgb,var(--dsw-static-green-500,#22c55e) 62%,var(--dsw-static-neutral-500,#7f8287)) 64%,var(--dsw-alias-label-primary));" +
|
|
2527
2709
|
// 热力档位用 label 补余而非 transparent:transparent 混色在浅色主题下
|
|
2528
2710
|
// 低档位对白底不足 3:1;label 补余两套主题各档都过线且单调。
|
|
2529
2711
|
"--mneme-heat-1:color-mix(in srgb,var(--dsw-alias-state-business-primary) 85%,var(--dsw-alias-label-primary));" +
|
|
2530
2712
|
"--mneme-heat-2:color-mix(in srgb,var(--dsw-alias-state-business-primary) 70%,var(--dsw-alias-label-primary));" +
|
|
2531
2713
|
"--mneme-heat-3:color-mix(in srgb,var(--dsw-alias-state-business-primary) 55%,var(--dsw-alias-label-primary));" +
|
|
2532
|
-
"--mneme-heat-4:color-mix(in srgb,var(--dsw-alias-state-business-primary) 40%,var(--dsw-alias-label-primary))" +
|
|
2714
|
+
"--mneme-heat-4:color-mix(in srgb,var(--dsw-alias-state-business-primary) 40%,var(--dsw-alias-label-primary));" +
|
|
2715
|
+
// 几何刻度层(视觉系统 P0,docs/plans/2026-11-10-panel-visual-system.md):
|
|
2716
|
+
// 颜色有 16 个 token 兜住,空间此前一个都没有——于是 padding 长出 107 种
|
|
2717
|
+
// 取值、只有 41.7% 落在 4 网格上、29% 是奇数像素。刻度按**像素值命名**
|
|
2718
|
+
// (--mneme-space-8 就是 8px),而不是 0..8 的档位号:
|
|
2719
|
+
// 档位号会让 gap:"4" 等于 8px,写的人要背一张表,读的人没法核对;
|
|
2720
|
+
// 值命名让调用点自解释,也让 test/panel-tokens.test.js 能用「允许的 px 集」
|
|
2721
|
+
// 直接校验,不需要第二份映射表。2/6/10 是半档(图标与文字之间、标题与卡之间),
|
|
2722
|
+
// 40 是滚动区末端的留白:24 之后一格跨度太窄,装不下滚到底时需要的呼吸。
|
|
2723
|
+
"--mneme-space-0:0;" +
|
|
2724
|
+
"--mneme-space-2:2px;" +
|
|
2725
|
+
"--mneme-space-4:4px;" +
|
|
2726
|
+
"--mneme-space-6:6px;" +
|
|
2727
|
+
"--mneme-space-8:8px;" +
|
|
2728
|
+
"--mneme-space-10:10px;" +
|
|
2729
|
+
"--mneme-space-12:12px;" +
|
|
2730
|
+
"--mneme-space-16:16px;" +
|
|
2731
|
+
"--mneme-space-20:20px;" +
|
|
2732
|
+
"--mneme-space-24:24px;" +
|
|
2733
|
+
"--mneme-space-40:40px;" +
|
|
2734
|
+
// 圆角:全文件此前 borderRadius 声明 0 处(圆角全靠宿主,各页不统一)。
|
|
2735
|
+
// 留两档是因为用途不同:8 是列表里的小卡,12 是仪表盘大卡(容器大、信息
|
|
2736
|
+
// 密度低,8 会让四角显得太紧)。
|
|
2737
|
+
"--mneme-radius-4:4px;" +
|
|
2738
|
+
"--mneme-radius-8:8px;" +
|
|
2739
|
+
"--mneme-radius-12:12px;" +
|
|
2740
|
+
"--mneme-radius-pill:999px;" +
|
|
2741
|
+
// 字号:现有 12/13/15 三种覆盖了绝大多数文本,把既有事实写成 token;
|
|
2742
|
+
// 10/14/18 仍散落在 133/61/52 处,属待迁移,不在此列(迁移要逐页视觉复核,
|
|
2743
|
+
// 全局替换会把紧凑列表撑开)。
|
|
2744
|
+
"--mneme-type-12:12px;" +
|
|
2745
|
+
"--mneme-type-13:13px;" +
|
|
2746
|
+
"--mneme-type-15:15px;" +
|
|
2747
|
+
"--mneme-weight-400:400;" +
|
|
2748
|
+
"--mneme-weight-600:600" +
|
|
2533
2749
|
"}",
|
|
2534
2750
|
".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);container-type:inline-size}",
|
|
2535
2751
|
// 窄容器(better-sidebar 工作台 ~500px)下中央 vtabs 会与右缘悬浮的
|
|
@@ -2662,6 +2878,33 @@ window.__ModuleLoader__.load({
|
|
|
2662
2878
|
".mneme-relrow:hover{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
2663
2879
|
".mneme-reltype{flex:none;font-size:11px;line-height:16px;color:var(--mneme-accent);border:1px solid color-mix(in srgb,var(--dsw-alias-state-business-primary) 35%,transparent);border-radius:6px;padding:0 6px}",
|
|
2664
2880
|
".mneme-relname{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
2881
|
+
// #619 可见化 B:情感边上的情绪词。刻意不加新色相——沿用 accent 的淡色调,
|
|
2882
|
+
// 因为它是「同一条边上的附加信息」,不是新的边类别;加色相会让人以为
|
|
2883
|
+
// affective_context 是第三种关系。trait 档只把字号/字重提一档,用来区分
|
|
2884
|
+
// 「一次场合」和「一贯倾向」,两档仍然同色。
|
|
2885
|
+
".mneme-relaffect{flex:none;font-size:11px;line-height:16px;color:var(--mneme-fg-dim);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:0 6px;max-width:9em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
2886
|
+
".mneme-relaffect--trait{color:var(--mneme-accent);font-weight:600}",
|
|
2887
|
+
// #619 可见化 C:情绪归因一览。沿用面板既有色,不引入新色相——
|
|
2888
|
+
// trait 用 accent 加重,其余靠灰阶分层,跟关系行那颗小标签是同一套语汇。
|
|
2889
|
+
".mneme-affective-head{margin-bottom:10px}",
|
|
2890
|
+
".mneme-affective-title{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary)}",
|
|
2891
|
+
".mneme-affective-sub{font-size:12px;color:var(--mneme-fg-dim);margin-top:2px;line-height:1.5}",
|
|
2892
|
+
".mneme-affective-list{display:flex;flex-direction:column;gap:6px;margin-top:8px}",
|
|
2893
|
+
".mneme-affective-row{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px 10px}",
|
|
2894
|
+
".mneme-affective-row--trait{border-color:var(--mneme-accent)}",
|
|
2895
|
+
".mneme-affective-main{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:13px}",
|
|
2896
|
+
".mneme-affective-person{font-weight:600;color:var(--dsw-alias-label-primary)}",
|
|
2897
|
+
".mneme-affective-emotion{font-weight:600;color:var(--dsw-alias-label-primary)}",
|
|
2898
|
+
".mneme-affective-row--trait .mneme-affective-emotion{color:var(--mneme-accent)}",
|
|
2899
|
+
".mneme-affective-arrow{color:var(--mneme-fg-dim)}",
|
|
2900
|
+
".mneme-affective-target{color:var(--dsw-alias-label-primary)}",
|
|
2901
|
+
".mneme-affective-tag{flex:none;font-size:11px;line-height:16px;color:var(--mneme-fg-dim);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:0 6px}",
|
|
2902
|
+
".mneme-affective-tag--trait{color:var(--mneme-accent);border-color:var(--mneme-accent);font-weight:600}",
|
|
2903
|
+
".mneme-affective-meta{font-size:11px;color:var(--mneme-fg-dim);margin-top:3px}",
|
|
2904
|
+
".mneme-affective-empty{font-size:12px;color:var(--mneme-fg-dim);line-height:1.6;padding:10px 0}",
|
|
2905
|
+
".mneme-affective-empty--error{color:var(--mneme-danger)}",
|
|
2906
|
+
".mneme-affective-note{font-size:11px;color:var(--mneme-fg-dim);margin-top:8px}",
|
|
2907
|
+
".mneme-gs-affective{margin-top:2px}",
|
|
2665
2908
|
// issue #24 块4 复核面:抬过的边。用虚线边色(--mneme-graphic-dim 同层)
|
|
2666
2909
|
// 而不是新增颜色——它表达的是「同一条边,只是被用过」,不是新类别。
|
|
2667
2910
|
".mneme-boosted{margin-top:6px;padding-top:6px;border-top:1px dashed var(--dsw-alias-border-l2)}",
|
|
@@ -2684,11 +2927,13 @@ window.__ModuleLoader__.load({
|
|
|
2684
2927
|
".mneme-btn:hover{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
2685
2928
|
".mneme-btn:disabled{opacity:.5;cursor:default}",
|
|
2686
2929
|
".mneme-saved{font-size:12px;color:var(--mneme-ok)}",
|
|
2687
|
-
// rule rows: numbered chips +
|
|
2688
|
-
|
|
2930
|
+
// rule rows: numbered chips + 常显的淡删除键(悬停/聚焦才满不透明)。
|
|
2931
|
+
// 曾经彻底 opacity:0,只有鼠标扫过才出现——在窄侧栏和触摸设备上等于没有删除入口,
|
|
2932
|
+
// 用户只能靠猜。留 .45 的常显态,噪声换可发现性。
|
|
2933
|
+
".mneme-set-row{display:flex;align-items:flex-start;gap:10px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--mneme-radius-8);margin-bottom:8px;background:var(--dsw-alias-bg-base,transparent)}",
|
|
2689
2934
|
".mneme-set-idx{flex:none;width:20px;height:20px;border-radius:50%;border:1px solid var(--dsw-alias-border-l2);color:var(--mneme-fg-dim);font-size:11px;line-height:18px;display:inline-flex;align-items:center;justify-content:center;font-variant-numeric:tabular-nums}",
|
|
2690
2935
|
".mneme-set-ruletext{flex:1;min-width:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary);word-break:break-word}",
|
|
2691
|
-
".mneme-set-del{flex:none;opacity
|
|
2936
|
+
".mneme-set-del{flex:none;opacity:.45;transition:opacity .12s;border:none;background:none;color:var(--mneme-fg-dim);cursor:pointer;font-size:14px;line-height:20px;padding:0 4px;border-radius:6px}",
|
|
2692
2937
|
".mneme-set-row:hover .mneme-set-del,.mneme-set-row:focus-within .mneme-set-del{opacity:1}",
|
|
2693
2938
|
".mneme-set-del:hover{color:var(--mneme-danger);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
2694
2939
|
// 人格卡删除 × 独立类:不复用 mneme-set-del——其 opacity:0 仅在
|
|
@@ -2702,7 +2947,6 @@ window.__ModuleLoader__.load({
|
|
|
2702
2947
|
".mneme-set-cmdname{font-size:13px;font-weight:600;color:var(--mneme-accent)}",
|
|
2703
2948
|
".mneme-set-cmddesc{font-size:12px;line-height:17px;color:var(--mneme-fg-dim);margin-top:1px;word-break:break-word}",
|
|
2704
2949
|
// --- settings sub-view ---
|
|
2705
|
-
".mneme-set{flex:1;min-height:0;overflow-y:auto;padding:8px 24px 48px;box-sizing:border-box}",
|
|
2706
2950
|
// --- 宿主中列整页面板(sidebar.panellist + main 成对座位)---
|
|
2707
2951
|
// 宿主把 main 槽渲染在三列 grid 的中列里,那一层只给了 min-width:0 与
|
|
2708
2952
|
// overflow:hidden,没有任何尺寸约束——注册者必须自己铺满并自管滚动,
|
|
@@ -2773,6 +3017,26 @@ window.__ModuleLoader__.load({
|
|
|
2773
3017
|
".mneme-wikitoggle.mneme-on{border-color:var(--dsw-alias-state-business-primary)}",
|
|
2774
3018
|
".mneme-wikistale{margin-inline-end:2px}",
|
|
2775
3019
|
".mneme-wikit1bar{flex:none;padding:6px 16px;border-top:1px solid var(--dsw-alias-border-l2);font-size:11.5px;line-height:16px;color:var(--mneme-fg-dim);background:var(--dsw-alias-bg-layer-1)}",
|
|
3020
|
+
// --- 证据复核(Grounded Claims)---
|
|
3021
|
+
// 回执条压在顶栏下面一条,三种结局共用同一块位置:位置固定,用户扫一眼
|
|
3022
|
+
// 就知道「这次点完是什么」。warn 档只换左边框颜色,不改正文色——正文色
|
|
3023
|
+
// 一动就得过 contrast.test.js 的重算,而这里没必要冒那个险。
|
|
3024
|
+
".mneme-wikidriftbar{flex:none;display:flex;flex-wrap:wrap;align-items:baseline;gap:6px;padding:6px 16px;border-bottom:1px solid var(--dsw-alias-border-l1);font-size:11.5px;line-height:17px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-base,transparent)}",
|
|
3025
|
+
".mneme-wikidriftbar--warn{border-left:3px solid var(--dsw-alias-state-business-primary);padding-left:13px;color:var(--dsw-alias-label-primary)}",
|
|
3026
|
+
".mneme-wikidriftnone{color:var(--mneme-fg-dim)}",
|
|
3027
|
+
// 树里的条数徽章:复用 pending 徽章的视觉,只压窄一点,避免挤掉模块名。
|
|
3028
|
+
".mneme-wikidriftn{margin-inline-start:6px;font-size:10.5px;line-height:14px}",
|
|
3029
|
+
".mneme-wikidriftn--flat{margin-inline-start:6px;color:var(--mneme-fg-dim)}",
|
|
3030
|
+
// 「这页比不了」和「这页没事」必须是两种视觉:前者是空心问号,后者什么都不挂。
|
|
3031
|
+
".mneme-wikidriftn--unknown{margin-inline-start:6px;color:var(--mneme-fg-dim);font-size:11px}",
|
|
3032
|
+
".mneme-wikidriftverified{font-size:11px;line-height:16px;color:var(--mneme-fg-dim);margin-bottom:10px}",
|
|
3033
|
+
// 当前这页的明细:读之前先给出「哪条、在哪、为什么」,不给判断(判断是人的)。
|
|
3034
|
+
".mneme-wikidriftpage{margin:0 0 14px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-1)}",
|
|
3035
|
+
".mneme-wikidriftpagehead{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary);margin-bottom:6px}",
|
|
3036
|
+
".mneme-wikidriftrow{display:flex;flex-wrap:wrap;gap:6px;align-items:baseline;padding:5px 0;border-top:1px solid var(--dsw-alias-border-l1)}",
|
|
3037
|
+
".mneme-wikidriftwhy{font-size:11.5px;line-height:16px;color:var(--dsw-alias-label-primary)}",
|
|
3038
|
+
".mneme-wikidriftpath{font-size:11px;line-height:16px;color:var(--mneme-fg-dim)}",
|
|
3039
|
+
".mneme-wikidriftstmt{flex-basis:100%;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}",
|
|
2776
3040
|
// --- 知识库页(契约 §3.6)---
|
|
2777
3041
|
// 左列复用 wikitree 的宽度与分隔线,只是树项改成了集合行;右边沿用
|
|
2778
3042
|
// wikipage 的阅读排版。颜色全部走语义层,不新增任何宿主 token。
|
|
@@ -2897,27 +3161,27 @@ window.__ModuleLoader__.load({
|
|
|
2897
3161
|
".mneme-badge--default{color:var(--mneme-fg-dim);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
2898
3162
|
".mneme-badge--changed{color:var(--mneme-accent);background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 10%,transparent);border:1px solid color-mix(in srgb,var(--dsw-alias-state-business-primary) 30%,transparent)}",
|
|
2899
3163
|
// 冲突集中处理队列(v0.8.0):状态页的待确认冲突列表
|
|
2900
|
-
".mneme-conflictq{display:flex;flex-direction:column;gap:
|
|
2901
|
-
".mneme-conflictq-head{display:flex;align-items:center;gap:
|
|
3164
|
+
".mneme-conflictq{display:flex;flex-direction:column;gap:var(--mneme-space-10)}",
|
|
3165
|
+
".mneme-conflictq-head{display:flex;align-items:center;gap:var(--mneme-space-8)}",
|
|
2902
3166
|
".mneme-conflictq-truncated{flex:1;font-size:12px;line-height:16px;color:var(--mneme-fg-dim);text-align:right}",
|
|
2903
|
-
".mneme-conflict-item{border:1px solid var(--dsw-alias-border-l1,var(--dsw-alias-interactive-bg-hover));border-radius:
|
|
3167
|
+
".mneme-conflict-item{border:1px solid var(--dsw-alias-border-l1,var(--dsw-alias-interactive-bg-hover));border-radius:var(--mneme-radius-8);padding:var(--mneme-space-10);display:flex;flex-direction:column;gap:var(--mneme-space-8)}",
|
|
2904
3168
|
".mneme-conflict-reason{font-size:12px;line-height:17px;color:var(--dsw-alias-label-secondary)}",
|
|
2905
|
-
".mneme-conflict-pair{display:grid;grid-template-columns:1fr 1fr;gap:
|
|
2906
|
-
".mneme-conflict-side{border:1px solid var(--dsw-alias-border-l1,var(--dsw-alias-interactive-bg-hover));border-radius:
|
|
3169
|
+
".mneme-conflict-pair{display:grid;grid-template-columns:1fr 1fr;gap:var(--mneme-space-8)}",
|
|
3170
|
+
".mneme-conflict-side{border:1px solid var(--dsw-alias-border-l1,var(--dsw-alias-interactive-bg-hover));border-radius:var(--mneme-radius-8);padding:var(--mneme-space-8);display:flex;flex-direction:column;gap:var(--mneme-space-4);min-width:0}",
|
|
2907
3171
|
".mneme-conflict-sidelabel{font-size:11px;color:var(--mneme-fg-dim)}",
|
|
2908
3172
|
".mneme-conflict-sidetitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
2909
3173
|
".mneme-conflict-snippet{font-size:12px;line-height:17px;color:var(--dsw-alias-label-secondary);display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}",
|
|
2910
3174
|
".mneme-conflict-missing{font-size:12px;color:var(--mneme-fg-dim)}",
|
|
2911
|
-
".mneme-conflict-actions{display:flex;align-items:center;gap:
|
|
3175
|
+
".mneme-conflict-actions{display:flex;align-items:center;gap:var(--mneme-space-6);flex-wrap:wrap}",
|
|
2912
3176
|
".mneme-conflict-hint{font-size:11px;color:var(--mneme-fg-dim);margin-right:auto}",
|
|
2913
3177
|
// #177 冲突队列视觉批次:A/B 侧色、相似度条、词级 diff、主色按钮、空态
|
|
2914
3178
|
".mneme-conflict-side--a{border-color:color-mix(in srgb,var(--dsw-alias-state-business-primary) 35%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 4%,transparent)}",
|
|
2915
3179
|
".mneme-conflict-side--a .mneme-conflict-sidelabel{color:var(--mneme-accent);font-weight:600}",
|
|
2916
3180
|
".mneme-conflict-side--b{border-color:color-mix(in srgb,var(--mneme-warn) 45%,transparent);background:color-mix(in srgb,var(--mneme-warn) 5%,transparent)}",
|
|
2917
3181
|
".mneme-conflict-side--b .mneme-conflict-sidelabel{color:var(--mneme-warn);font-weight:600}",
|
|
2918
|
-
".mneme-conflict-simrow{display:flex;align-items:center;gap:
|
|
2919
|
-
".mneme-conflict-simbar{position:relative;flex:1;height:5px;border-radius:
|
|
2920
|
-
".mneme-conflict-simfill{position:absolute;top:0;bottom:0;left:0;border-radius:
|
|
3182
|
+
".mneme-conflict-simrow{display:flex;align-items:center;gap:var(--mneme-space-8);font-size:11px;color:var(--dsw-alias-label-secondary)}",
|
|
3183
|
+
".mneme-conflict-simbar{position:relative;flex:1;height:5px;border-radius:var(--mneme-radius-pill);background:var(--dsw-alias-interactive-bg-hover);overflow:hidden}",
|
|
3184
|
+
".mneme-conflict-simfill{position:absolute;top:0;bottom:0;left:0;border-radius:var(--mneme-radius-pill);background:var(--dsw-alias-state-business-primary)}",
|
|
2921
3185
|
".mneme-conflict-diff{font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary);display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden}",
|
|
2922
3186
|
// 展开全文:取消行数钳制并给一个内部滚动区——报障点是「窗口太小又不能
|
|
2923
3187
|
// 放大,完全对比不了内容」,所以展开态必须能容纳长文本且不撑破面板。
|
|
@@ -2925,25 +3189,25 @@ window.__ModuleLoader__.load({
|
|
|
2925
3189
|
// 页),那时 46vh 会算成 0px,展开态直接看不见字——实测踩过。
|
|
2926
3190
|
".mneme-conflict-diff--full{display:block;-webkit-line-clamp:unset;max-height:28em;overflow-y:auto}",
|
|
2927
3191
|
".mneme-conflict-text{font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary);white-space:pre-wrap;overflow-wrap:anywhere;max-height:28em;overflow-y:auto}",
|
|
2928
|
-
".mneme-conflict-meta{display:flex;align-items:center;gap:
|
|
3192
|
+
".mneme-conflict-meta{display:flex;align-items:center;gap:var(--mneme-space-6);flex-wrap:wrap;font-size:11px;line-height:16px;color:var(--mneme-fg-dim)}",
|
|
2929
3193
|
".mneme-conflict-metaval{color:var(--dsw-alias-label-secondary)}",
|
|
2930
3194
|
// 窄面板下并排会把每侧挤到无法阅读,退成单列(容器查询按面板实际宽度判)
|
|
2931
3195
|
"@container (max-width:560px){.mneme-conflict-pair{grid-template-columns:1fr}}",
|
|
2932
|
-
".mneme-conflict-mark{text-decoration:none;border-radius:
|
|
3196
|
+
".mneme-conflict-mark{text-decoration:none;border-radius:var(--mneme-radius-4);padding:0 1px}",
|
|
2933
3197
|
".mneme-conflict-mark--del{background:color-mix(in srgb,var(--mneme-danger) 16%,transparent);text-decoration:line-through}",
|
|
2934
3198
|
".mneme-conflict-mark--ins{background:color-mix(in srgb,var(--mneme-ok) 18%,transparent)}",
|
|
2935
|
-
".mneme-conflict-primary{display:inline-flex;align-items:center;gap:
|
|
3199
|
+
".mneme-conflict-primary{display:inline-flex;align-items:center;gap:var(--mneme-space-4);height:24px;padding:0 var(--mneme-space-10);border-radius:var(--mneme-radius-8);border:none;background:var(--mneme-accent);color:var(--dsw-alias-bg-layer-1);cursor:pointer;font-family:inherit;font-size:12px;line-height:16px}",
|
|
2936
3200
|
".mneme-conflict-primary:disabled{opacity:.55;cursor:default}",
|
|
2937
3201
|
".mneme-conflict-primary:hover:not(:disabled){background:color-mix(in srgb,var(--mneme-accent) 85%,var(--dsw-alias-label-primary))}",
|
|
2938
3202
|
".mneme-badge--frozen{color:var(--mneme-warn);background:color-mix(in srgb,var(--mneme-warn) 12%,transparent)}",
|
|
2939
3203
|
// 「待重启」徽标:沿用 frozen 的 warn 语义(都是「现状不是你刚设的样子」),
|
|
2940
3204
|
// 但加边框把它和只读冻结态区分开——它需要用户动手,不是终态。
|
|
2941
3205
|
".mneme-badge--pending{color:var(--mneme-warn);background:color-mix(in srgb,var(--mneme-warn) 12%,transparent);border:1px solid color-mix(in srgb,var(--mneme-warn) 30%,transparent)}",
|
|
2942
|
-
".mneme-pending{display:flex;align-items:flex-start;gap:
|
|
3206
|
+
".mneme-pending{display:flex;align-items:flex-start;gap:var(--mneme-space-8);margin:var(--mneme-space-8) 0 0;padding:var(--mneme-space-8) var(--mneme-space-12);border-radius:var(--mneme-radius-8);font-size:12.5px;line-height:18px;color:var(--mneme-warn);background:color-mix(in srgb,var(--mneme-warn) 10%,transparent);border:1px solid color-mix(in srgb,var(--mneme-warn) 28%,transparent)}",
|
|
2943
3207
|
// 圆点用当前色(currentColor)而不是再引一次 --mneme-warn:色值只有一个真源,
|
|
2944
3208
|
// 改主题时提示行与圆点不会走散。
|
|
2945
|
-
".mneme-pending-dot{flex:none;width:6px;height:6px;margin-top:
|
|
2946
|
-
".mneme-conflict-empty{border:1px dashed var(--dsw-alias-border-l2);border-radius:
|
|
3209
|
+
".mneme-pending-dot{flex:none;width:6px;height:6px;margin-top:var(--mneme-space-6);border-radius:50%;background:currentColor}",
|
|
3210
|
+
".mneme-conflict-empty{border:1px dashed var(--dsw-alias-border-l2);border-radius:var(--mneme-radius-8);padding:var(--mneme-space-12);display:flex;flex-direction:column;gap:var(--mneme-space-4)}",
|
|
2947
3211
|
".mneme-conflict-empty-title{font-size:12.5px;font-weight:600;color:var(--dsw-alias-label-secondary)}",
|
|
2948
3212
|
".mneme-conflict-empty-body{font-size:12px;line-height:18px;color:var(--mneme-fg-dim)}",
|
|
2949
3213
|
".mneme-statuscard--actionable{border-color:color-mix(in srgb,var(--mneme-warn) 55%,transparent);box-shadow:0 0 0 1px color-mix(in srgb,var(--mneme-warn) 35%,transparent)}",
|
|
@@ -2951,7 +3215,7 @@ window.__ModuleLoader__.load({
|
|
|
2951
3215
|
".mneme-triggerwrap{position:relative;pointer-events:none}",
|
|
2952
3216
|
".mneme-triggerwrap .mneme-trigger{pointer-events:auto}",
|
|
2953
3217
|
".mneme-conflict-jump .mneme-statuscard{border-color:color-mix(in srgb,var(--mneme-warn) 55%,transparent);box-shadow:0 0 0 1px color-mix(in srgb,var(--mneme-warn) 35%,transparent)}",
|
|
2954
|
-
".mneme-conflict-jump:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px;border-radius:
|
|
3218
|
+
".mneme-conflict-jump:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px;border-radius:var(--mneme-radius-12)}",
|
|
2955
3219
|
// 详情抽屉:sheet 内右侧滑出,覆盖在浏览区之上
|
|
2956
3220
|
".mneme-xmain{position:relative}",
|
|
2957
3221
|
"@keyframes mneme-slidein{from{opacity:0;transform:translateX(16px)}to{opacity:1;transform:translateX(0)}}",
|
|
@@ -3007,8 +3271,8 @@ window.__ModuleLoader__.load({
|
|
|
3007
3271
|
".mneme-xmore{display:flex;justify-content:center;align-items:center;padding:10px 0 24px;min-height:20px}",
|
|
3008
3272
|
".mneme-xemptyico{display:block;margin:0 auto 6px;opacity:.7}",
|
|
3009
3273
|
// --- status sub-view: responsive stat-card grid (auto-fill, ~220px min) ---
|
|
3010
|
-
".mneme-status{flex:1;min-height:0;overflow-y:auto;padding:
|
|
3011
|
-
".mneme-statusgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:
|
|
3274
|
+
".mneme-status{flex:1;min-height:0;overflow-y:auto;padding:var(--mneme-space-20) var(--mneme-space-24) var(--mneme-space-40);box-sizing:border-box}",
|
|
3275
|
+
".mneme-statusgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:var(--mneme-space-12);max-width:1000px;margin:0 auto var(--mneme-space-12)}",
|
|
3012
3276
|
".mneme-statusgrid:last-of-type{margin-bottom:0}",
|
|
3013
3277
|
// 概览区是单行三卡:auto-fit 让不满的轨道塌缩,三张卡恒等宽铺满、行尾不留洞
|
|
3014
3278
|
".mneme-statusgrid--overview{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}",
|
|
@@ -3017,13 +3281,13 @@ window.__ModuleLoader__.load({
|
|
|
3017
3281
|
"@container (min-width:452px) and (max-width:683px){.mneme-statuscard--vectorwide{grid-column:span 2}}",
|
|
3018
3282
|
"@container (min-width:452px){.mneme-statuscard--wide{grid-column:span 2}}",
|
|
3019
3283
|
// 状态页分组标题(库内一览 / 后台运转):只做扫读锚点,压得比卡片标题低
|
|
3020
|
-
".mneme-statushead{max-width:1000px;margin:0 auto
|
|
3021
|
-
".mneme-statusgrid+.mneme-statushead{margin-top:
|
|
3022
|
-
".mneme-statuscard{min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:
|
|
3284
|
+
".mneme-statushead{max-width:1000px;margin:0 auto var(--mneme-space-10);font-size:12px;font-weight:600;letter-spacing:.02em;color:var(--mneme-fg-dim)}",
|
|
3285
|
+
".mneme-statusgrid+.mneme-statushead{margin-top:var(--mneme-space-24)}",
|
|
3286
|
+
".mneme-statuscard{min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--mneme-radius-12);padding:var(--mneme-space-16) var(--mneme-space-16) var(--mneme-space-12)}",
|
|
3023
3287
|
// 指标行:一行一指标(label 左 / 数值右),取代「·」串联的说明长句——
|
|
3024
3288
|
// 拥挤感的根源是把多指标压进一段会换行的散文
|
|
3025
|
-
".mneme-statrows{margin-top:
|
|
3026
|
-
".mneme-statrow{display:flex;align-items:baseline;justify-content:space-between;gap:
|
|
3289
|
+
".mneme-statrows{margin-top:var(--mneme-space-10);display:flex;flex-direction:column;gap:var(--mneme-space-4)}",
|
|
3290
|
+
".mneme-statrow{display:flex;align-items:baseline;justify-content:space-between;gap:var(--mneme-space-12);font-size:12px;line-height:17px;color:var(--mneme-fg-dim)}",
|
|
3027
3291
|
".mneme-statrow>span:first-child{flex:none;white-space:nowrap}",
|
|
3028
3292
|
".mneme-statrow .mneme-heatdot{align-self:center}",
|
|
3029
3293
|
// 预警行(#275 库体积卡):行与值都要落预警色——.mneme-statrowval 自带
|
|
@@ -3031,9 +3295,9 @@ window.__ModuleLoader__.load({
|
|
|
3031
3295
|
".mneme-statrow--warn{color:var(--mneme-warn)}",
|
|
3032
3296
|
".mneme-statrow--warn .mneme-statrowval{color:var(--mneme-warn)}",
|
|
3033
3297
|
".mneme-statrowval{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;text-align:right}",
|
|
3034
|
-
".mneme-statfoot{margin-top:
|
|
3298
|
+
".mneme-statfoot{margin-top:var(--mneme-space-8);font-size:11px;line-height:16px;color:var(--mneme-fg-dim)}",
|
|
3035
3299
|
// 热度分布条:三档占比一图看清,行标只补数字(类名带双横线,与 heatdot 同款)
|
|
3036
|
-
".mneme-heatbar{display:flex;height:6px;border-radius:
|
|
3300
|
+
".mneme-heatbar{display:flex;height:6px;border-radius:var(--mneme-radius-pill);overflow:hidden;margin-top:var(--mneme-space-12);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
3037
3301
|
".mneme-heatbar-seg{min-width:2px}",
|
|
3038
3302
|
".mneme-heatbar--hot{background:var(--mneme-warn)}",
|
|
3039
3303
|
".mneme-heatbar--warm{background:var(--mneme-fg-dim)}",
|
|
@@ -3045,10 +3309,10 @@ window.__ModuleLoader__.load({
|
|
|
3045
3309
|
// --- Token 去向三屏(2026-10 可观测性改造):KPI 带 / donut / 7 天柱状 / 8 周热力 ---
|
|
3046
3310
|
// KPI 带固定 4 列、容器收窄降 2 列:用 @container 不用 @media——面板同时长在
|
|
3047
3311
|
// 宿主侧栏(~500px)与 sheet(1240px)里,决定布局的是容器宽度而非视口。
|
|
3048
|
-
".mneme-kpiband{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:
|
|
3312
|
+
".mneme-kpiband{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--mneme-space-12);max-width:1000px;margin:0 auto var(--mneme-space-12)}",
|
|
3049
3313
|
"@container (max-width:560px){.mneme-kpiband{grid-template-columns:repeat(2,minmax(0,1fr))}}",
|
|
3050
|
-
".mneme-tokenflow{max-width:1000px;margin:0 auto
|
|
3051
|
-
".mneme-donutwrap{display:flex;gap:
|
|
3314
|
+
".mneme-tokenflow{max-width:1000px;margin:0 auto var(--mneme-space-12);border:1px solid var(--dsw-alias-border-l2);border-radius:var(--mneme-radius-12);padding:var(--mneme-space-16) var(--mneme-space-16) var(--mneme-space-12)}",
|
|
3315
|
+
".mneme-donutwrap{display:flex;gap:var(--mneme-space-20);align-items:center;margin-top:var(--mneme-space-12)}",
|
|
3052
3316
|
"@container (max-width:480px){.mneme-donutwrap{flex-direction:column;align-items:stretch}.mneme-donutbox{align-self:center}}",
|
|
3053
3317
|
".mneme-donutbox{position:relative;flex:none;width:148px;height:148px}",
|
|
3054
3318
|
".mneme-donutbox svg{width:100%;height:100%;display:block}",
|
|
@@ -3063,7 +3327,7 @@ window.__ModuleLoader__.load({
|
|
|
3063
3327
|
".mneme-seg--3{stroke:var(--mneme-chart-3)}",
|
|
3064
3328
|
".mneme-seg--4{stroke:var(--mneme-chart-4)}",
|
|
3065
3329
|
".mneme-seg--5{stroke:var(--mneme-chart-5)}",
|
|
3066
|
-
".mneme-seg--6{stroke:
|
|
3330
|
+
".mneme-seg--6{stroke:var(--mneme-chart-6)}",
|
|
3067
3331
|
".mneme-legend{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}",
|
|
3068
3332
|
".mneme-legendrow{display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:8px;cursor:default;font-size:12.5px;line-height:18px;color:var(--dsw-alias-label-primary)}",
|
|
3069
3333
|
".mneme-legendrow:hover,.mneme-legendrow.focused{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
@@ -3074,7 +3338,7 @@ window.__ModuleLoader__.load({
|
|
|
3074
3338
|
".mneme-dot--3{background:var(--mneme-chart-3)}",
|
|
3075
3339
|
".mneme-dot--4{background:var(--mneme-chart-4)}",
|
|
3076
3340
|
".mneme-dot--5{background:var(--mneme-chart-5)}",
|
|
3077
|
-
".mneme-dot--6{background:
|
|
3341
|
+
".mneme-dot--6{background:var(--mneme-chart-6)}",
|
|
3078
3342
|
".mneme-legendname{color:var(--dsw-alias-label-primary);white-space:nowrap}",
|
|
3079
3343
|
".mneme-legendsub{color:var(--mneme-fg-dim);font-size:11px}",
|
|
3080
3344
|
".mneme-legendval{margin-left:auto;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary);white-space:nowrap}",
|
|
@@ -3113,35 +3377,35 @@ window.__ModuleLoader__.load({
|
|
|
3113
3377
|
".mneme-flowsink{min-width:0;display:flex;flex-direction:column}",
|
|
3114
3378
|
// 8 周沉淀只画两条(进来的 / 稳住的):220px 宽里三序列会细到看不清,
|
|
3115
3379
|
// 归档数改在下方图例里给总量。
|
|
3116
|
-
".mneme-sinkchart{display:flex;align-items:flex-end;gap:
|
|
3117
|
-
".mneme-sinkcol{flex:1;min-width:0;display:flex;align-items:flex-end;gap:
|
|
3118
|
-
".mneme-sinkbar{flex:1;min-width:0;border-radius:
|
|
3119
|
-
".mneme-sinkbar--added{background:var(--mneme-
|
|
3120
|
-
".mneme-sinkbar--merged{background:var(--mneme-
|
|
3121
|
-
".mneme-sinklabels{display:flex;gap:
|
|
3380
|
+
".mneme-sinkchart{display:flex;align-items:flex-end;gap:var(--mneme-space-4);height:96px;margin-top:var(--mneme-space-12)}",
|
|
3381
|
+
".mneme-sinkcol{flex:1;min-width:0;display:flex;align-items:flex-end;gap:var(--mneme-space-2);height:100%}",
|
|
3382
|
+
".mneme-sinkbar{flex:1;min-width:0;border-radius:var(--mneme-radius-4) var(--mneme-radius-4) 0 0;min-height:1px}",
|
|
3383
|
+
".mneme-sinkbar--added{background:var(--mneme-sink-added)}",
|
|
3384
|
+
".mneme-sinkbar--merged{background:var(--mneme-sink-merged)}",
|
|
3385
|
+
".mneme-sinklabels{display:flex;gap:var(--mneme-space-4);margin-top:var(--mneme-space-4);font-size:9.5px;color:var(--mneme-fg-dim);font-variant-numeric:tabular-nums}",
|
|
3122
3386
|
".mneme-sinklabels span{flex:1;min-width:0;text-align:center;white-space:nowrap}",
|
|
3123
|
-
".mneme-sinklegend{display:flex;flex-wrap:wrap;align-items:center;gap:
|
|
3387
|
+
".mneme-sinklegend{display:flex;flex-wrap:wrap;align-items:center;gap:var(--mneme-space-4) var(--mneme-space-10);margin-top:var(--mneme-space-8);font-size:10.5px;color:var(--mneme-fg-dim)}",
|
|
3124
3388
|
".mneme-sinklegend b{font-weight:600;color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}",
|
|
3125
3389
|
// 原 9 卡降级折叠区:默认收起,展开才挂载子卡(见 TokenFlowSection 注释)。
|
|
3126
|
-
".mneme-legacy{max-width:1000px;margin:0 auto
|
|
3390
|
+
".mneme-legacy{max-width:1000px;margin:0 auto var(--mneme-space-12);border:1px solid var(--dsw-alias-border-l2);border-radius:var(--mneme-radius-12);padding:var(--mneme-space-12) var(--mneme-space-16)}",
|
|
3127
3391
|
".mneme-legacy summary{cursor:pointer;font-size:13px;font-weight:600;color:var(--dsw-alias-label-secondary)}",
|
|
3128
3392
|
// --- 状态页工作台:让用户看见插件在干活(动态/沉淀/归档) ---
|
|
3129
|
-
".mneme-wbhead{margin:
|
|
3130
|
-
".mneme-wblist{max-width:1000px;display:flex;flex-direction:column;gap:
|
|
3131
|
-
".mneme-wbrow{display:flex;gap:
|
|
3132
|
-
".mneme-wbdot{flex:none;width:8px;height:8px;border-radius:50%;margin-top:
|
|
3393
|
+
".mneme-wbhead{margin:var(--mneme-space-24) auto var(--mneme-space-10);font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);max-width:1000px;display:flex;align-items:baseline;justify-content:space-between;gap:var(--mneme-space-8)}",
|
|
3394
|
+
".mneme-wblist{max-width:1000px;display:flex;flex-direction:column;gap:var(--mneme-space-6)}",
|
|
3395
|
+
".mneme-wbrow{display:flex;gap:var(--mneme-space-10);align-items:flex-start;padding:var(--mneme-space-10) var(--mneme-space-12);border:1px solid var(--dsw-alias-border-l1);border-radius:var(--mneme-radius-8)}",
|
|
3396
|
+
".mneme-wbdot{flex:none;width:8px;height:8px;border-radius:50%;margin-top:var(--mneme-space-6);background:var(--mneme-ok)}",
|
|
3133
3397
|
".mneme-wbdot--err{background:var(--mneme-danger)}",
|
|
3134
3398
|
".mneme-wbmain{min-width:0;flex:1}",
|
|
3135
3399
|
".mneme-wbtitle{font-size:13px;line-height:19px;color:var(--dsw-alias-label-primary)}",
|
|
3136
3400
|
".mneme-wbsub{font-size:12px;line-height:17px;color:var(--mneme-fg-dim);margin-top:1px}",
|
|
3137
|
-
".mneme-wbmemo{display:flex;gap:
|
|
3401
|
+
".mneme-wbmemo{display:flex;gap:var(--mneme-space-10);align-items:center;padding:var(--mneme-space-10) var(--mneme-space-12);border:1px solid var(--dsw-alias-border-l1);border-radius:var(--mneme-radius-8)}",
|
|
3138
3402
|
".mneme-wbmemo .mneme-xdot{flex:none}",
|
|
3139
3403
|
".mneme-wbmemo-main{min-width:0;flex:1}",
|
|
3140
3404
|
".mneme-statusnum{font-size:24px;font-weight:600;line-height:32px;color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums}",
|
|
3141
3405
|
// 大数字预警色(#275 库体积卡):状态卡都走 StatusCard,只有传 numWarn 的
|
|
3142
3406
|
// 卡会挂这个类。
|
|
3143
3407
|
".mneme-statusnum--warn{color:var(--mneme-warn)}",
|
|
3144
|
-
".mneme-statuscap{margin-top:
|
|
3408
|
+
".mneme-statuscap{margin-top:var(--mneme-space-4);font-size:13px;line-height:19px;color:var(--mneme-fg-dim);word-break:break-word}",
|
|
3145
3409
|
// --- destructive actions: red outline = delete, solid red = confirm ---
|
|
3146
3410
|
".mneme-btndanger{color:var(--mneme-danger);border-color:var(--mneme-danger)}",
|
|
3147
3411
|
".mneme-btndanger:hover{background:color-mix(in srgb,var(--mneme-danger) 12%,transparent)}",
|
|
@@ -3150,7 +3414,7 @@ window.__ModuleLoader__.load({
|
|
|
3150
3414
|
// --- settings cards: boxed cards for the runtime-mode and external-API
|
|
3151
3415
|
// sections — each card owns its fetch/PUT state, so it renders as a
|
|
3152
3416
|
// self-contained unit inside the stacked settings view ---
|
|
3153
|
-
".mneme-set-card{border:1px solid var(--dsw-alias-border-l2);border-radius:
|
|
3417
|
+
".mneme-set-card{border:1px solid var(--dsw-alias-border-l2);border-radius:var(--mneme-radius-12);padding:16px;margin-bottom:10px}",
|
|
3154
3418
|
// 设置页分组标题(个性化 / 记忆引擎 / 搜索 / 连接与安全 / 帮助):
|
|
3155
3419
|
// 先给「这一段在管什么」,卡片才是具体选项;scroll-margin-top 是给 #518 的粘顶目录(实测约 74px 高)留的跳转余量
|
|
3156
3420
|
// ——没有它,点 chip 滚过去的标题就停在目录背面
|
|
@@ -3163,7 +3427,7 @@ window.__ModuleLoader__.load({
|
|
|
3163
3427
|
".mneme-set-toc{position:sticky;top:0;z-index:2;background:var(--dsw-alias-bg-base,var(--dsw-alias-bg-layer-1));padding:8px 0 10px;margin-bottom:4px;border-bottom:1px solid var(--dsw-alias-border-l2)}",
|
|
3164
3428
|
".mneme-set-toclabel{font-size:11px;font-weight:600;letter-spacing:.02em;color:var(--mneme-fg-dim);margin-bottom:6px}",
|
|
3165
3429
|
".mneme-set-toclist{display:flex;gap:6px;overflow-x:auto;padding:3px}", // 不 wrap:chip flex:none,窄屏横向滚;3px 给 focus ring 留位(滚动容器会裁掉描边)
|
|
3166
|
-
".mneme-set-tocchip{flex:none;height:26px;padding:0 10px;border-radius:
|
|
3430
|
+
".mneme-set-tocchip{flex:none;height:26px;padding:0 10px;border-radius:var(--mneme-radius-pill);border:1px solid var(--dsw-alias-border-l2);background:none;color:var(--dsw-alias-label-secondary);cursor:pointer;font-family:inherit;font-size:12px;line-height:24px;transition:border-color .12s,color .12s}",
|
|
3167
3431
|
".mneme-set-tocchip:hover{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-primary)}",
|
|
3168
3432
|
".mneme-set-tocchip:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}",
|
|
3169
3433
|
".mneme-set-token{font-family:monospace;font-size:12px;padding:7px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-base,transparent);color:var(--dsw-alias-label-primary);word-break:break-all;user-select:all}",
|
|
@@ -3372,14 +3636,18 @@ window.__ModuleLoader__.load({
|
|
|
3372
3636
|
"--mneme-accent:color-mix(in srgb,var(--dsw-alias-state-business-primary) 85%,var(--dsw-alias-label-primary));" +
|
|
3373
3637
|
"--mneme-danger:color-mix(in srgb,var(--dsw-alias-state-error-primary) 80%,var(--dsw-alias-label-primary));" +
|
|
3374
3638
|
"--mneme-warn:color-mix(in srgb,var(--dsw-static-amber-600,#dd8629) 65%,var(--dsw-alias-label-primary));" +
|
|
3375
|
-
"--mneme-ok:color-mix(in srgb,var(--dsw-static-green-500,#22c55e) 65%,var(--dsw-alias-label-primary))" +
|
|
3639
|
+
"--mneme-ok:color-mix(in srgb,var(--dsw-static-green-500,#22c55e) 65%,var(--dsw-alias-label-primary));" +
|
|
3640
|
+
// 格线专用:纸面合成色上 1px 线要看得见但不抢字。宿主 border-l1 只有 3.9%/5.9%,
|
|
3641
|
+
// 与纸的对比 1.09:1(浅)/1.20:1(深),1px 线在这个量级等于没有;20% 是
|
|
3642
|
+
// 1.55:1 / 1.88:1,14% 偏淡(1.35/1.55),26% 开始压字。深浅各开一支不值。
|
|
3643
|
+
"--mneme-rule:color-mix(in srgb,var(--dsw-alias-label-primary) 20%,transparent)" +
|
|
3376
3644
|
"}",
|
|
3377
3645
|
// 手绘风只靠**形状**做,不靠颜色:四角不对称的圆角(每边一个数会写成
|
|
3378
3646
|
// 「横向/纵向」两组百分比)、比常规粗一点的 1.5px 描边、虚线分隔、以及
|
|
3379
3647
|
// 正负 0.3deg 的轻微歪斜——这些都不碰对比度,深浅主题下一样成立。
|
|
3380
3648
|
// 想加纸纹就用 repeating-linear-gradient:它不是 color 声明,主锁不看;
|
|
3381
3649
|
// 反过来**别在这里加 color**,任何新色值都要先过 #178 的 AA 主锁。
|
|
3382
|
-
".mneme-diary{display:flex;flex-direction:column;gap:10px;height:100%;min-height:0;
|
|
3650
|
+
".mneme-diary{display:flex;flex-direction:column;gap:10px;height:100%;min-height:0;font-size:13px;line-height:19px;color:var(--dsw-alias-label-primary);box-sizing:border-box}",
|
|
3383
3651
|
".mneme-diary-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}",
|
|
3384
3652
|
".mneme-diary-title{font-size:15px;line-height:21px;font-weight:700;letter-spacing:.3px;color:var(--dsw-alias-label-primary)}",
|
|
3385
3653
|
".mneme-diary-note{font-size:12px;line-height:17px;color:var(--mneme-fg-dim)}",
|
|
@@ -3416,29 +3684,43 @@ window.__ModuleLoader__.load({
|
|
|
3416
3684
|
// 不另起弹层——面板里已经有纸张在动,再多一层浮层会盖住正文。
|
|
3417
3685
|
".mneme-diary-notice{flex:none;font-size:12px;line-height:18px;padding:5px 9px;border-radius:6px;border-left:3px solid var(--mneme-accent);background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary);word-break:break-word}",
|
|
3418
3686
|
".mneme-diary-notice--bad{border-left-color:var(--mneme-danger);color:var(--mneme-danger)}",
|
|
3419
|
-
".mneme-diary-paperhead{display:flex;align-items:baseline;gap:8px;padding-bottom:6px;margin-bottom:6px;border-bottom:1px
|
|
3687
|
+
".mneme-diary-paperhead{display:flex;align-items:baseline;gap:8px;padding-bottom:6px;margin-bottom:6px;border-bottom:1px dashed var(--dsw-alias-border-l1)}",
|
|
3420
3688
|
".mneme-diary-papername{font-size:13px;line-height:19px;font-weight:600;color:var(--mneme-accent);flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
3421
3689
|
".mneme-diary-paperdate{font-size:11px;line-height:16px;color:var(--mneme-fg-dim);font-variant-numeric:tabular-nums;flex:none}",
|
|
3422
|
-
// 纸地:素纸 / 横线 /
|
|
3423
|
-
//
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3690
|
+
// 纸地:素纸 / 横线 / 点阵。(牛皮已删:它只是「给纸面上 3%→8% 同一层色」,
|
|
3691
|
+
// 与素纸平均通道差 5.7 级、浅色下只差 1.01:1,且是这块唯一硬编码 hex。)
|
|
3692
|
+
//
|
|
3693
|
+
// 纹路挂在**正文元素**上而不是纸上,这是这段唯一的非常规处,两个理由:
|
|
3694
|
+
// ① 挂在纸上时,这几条与下面那条基础规则是**同一个类选择器**、特异性打平,
|
|
3695
|
+
// 基础规则在数组里更靠后且用的是 background 简写,会把 background-image 与
|
|
3696
|
+
// background-color 一起复位——「四种纸面一模一样」就是这么来的,注释里
|
|
3697
|
+
// 「格线正好落在 26px 行高上」的好意从来没生效过。换成后代选择器后两者
|
|
3698
|
+
// 不再是同一个元素,冲突从结构上消失,不靠源码顺序。
|
|
3699
|
+
// ② 纸面在两处渲染:右栏 pane 里纸内第一个孩子直接是 .mneme-diary-text,面板
|
|
3700
|
+
// tab 里纸内先是 .mneme-diary-paperhead——同一个 background-position 一处要
|
|
3701
|
+
// 12px、另一处要 32px,挂在纸上永远对不齐一处;挂在拥有 26px 行高的元素上,
|
|
3702
|
+
// 纹路跟着行盒走,两处天然对齐。行高若改,这里的 25px/26px 要同步。
|
|
3703
|
+
".mneme-diary-paper--ruled .mneme-diary-text{background-image:repeating-linear-gradient(to bottom,transparent 0 25px,var(--mneme-rule) 25px 26px);background-size:100% 26px;background-repeat:repeat}",
|
|
3704
|
+
// 点阵:横竖都 26px 一个点,点落在行盒正中。选点阵不选方格,是因为 14px 中文
|
|
3705
|
+
// 衬线正文里竖线会从字中间穿过去(20% 时尤其明显),点只占一个行盒中心,
|
|
3706
|
+
// 二维结构还在,正文不受干扰。点半径用 em 而不是 px:随字号走,也不进 px 棘轮。
|
|
3707
|
+
".mneme-diary-paper--grid .mneme-diary-text{background-image:radial-gradient(circle at 50% 50%,var(--mneme-rule) 0 .08em,transparent .1em);background-size:26px 26px;background-repeat:repeat}",
|
|
3708
|
+
// 素纸没有纹路,只靠这层 3% 纸色。**必须写 background-color**:简写会把上面两条
|
|
3709
|
+
// 的 background-image 清掉,这正是这个 bug 的形状,别再写回去。
|
|
3710
|
+
".mneme-diary-paper{flex:1 1 auto;min-width:0;overflow:auto;padding:12px 14px;border:1.5px solid var(--dsw-alias-border-l1);border-radius:16px 11px 15px 9px;transform:rotate(.18deg);background-color:color-mix(in srgb,var(--dsw-alias-label-primary) 3%,transparent)}",
|
|
3429
3711
|
// 日记正文用衬线:本子上写字不该像聊天记录。四套纸共用一套字,行高 26px
|
|
3430
3712
|
// 与 --ruled/--grid 的格线对齐;行高一改必须同步上面两条 background-size。
|
|
3431
|
-
".mneme-diary-text{margin:0;word-break:break-word;font-family:Georgia,\"Times New Roman\",\"Songti SC\",\"Noto Serif SC\",serif;font-size:14px;line-height:26px;color:var(--dsw-alias-label-primary)}",
|
|
3713
|
+
".mneme-diary-text{margin:0;min-height:100%;box-sizing:border-box;word-break:break-word;font-family:Georgia,\"Times New Roman\",\"Songti SC\",\"Noto Serif SC\",serif;font-size:14px;line-height:26px;color:var(--dsw-alias-label-primary)}",
|
|
3432
3714
|
// 正文是渲染过的排版(renderDiaryBody),不再是 <pre> 原文:段落、小标题、
|
|
3433
3715
|
// 列表各归各位。行高 26px 必须贯穿所有块级元素,否则格线会和字错开。
|
|
3434
|
-
".mneme-diary-text p{margin:0 0
|
|
3716
|
+
".mneme-diary-text p{margin:0 0 1lh}",
|
|
3435
3717
|
".mneme-diary-text p:last-child{margin-bottom:0}",
|
|
3436
|
-
".mneme-diary-text h2,.mneme-diary-text h3,.mneme-diary-text h4,.mneme-diary-text h5{margin:
|
|
3718
|
+
".mneme-diary-text h2,.mneme-diary-text h3,.mneme-diary-text h4,.mneme-diary-text h5{margin:1lh 0 0;font-family:inherit;font-weight:600;line-height:26px;color:var(--dsw-alias-label-primary)}",
|
|
3437
3719
|
".mneme-diary-text h2{font-size:16px}",
|
|
3438
3720
|
".mneme-diary-text h3{font-size:15px}",
|
|
3439
|
-
".mneme-diary-text ul,.mneme-diary-text ol{margin:0 0
|
|
3721
|
+
".mneme-diary-text ul,.mneme-diary-text ol{margin:0 0 1lh;padding-left:22px}",
|
|
3440
3722
|
".mneme-diary-text li{margin:0;line-height:26px}",
|
|
3441
|
-
".mneme-diary-text blockquote{margin:0 0
|
|
3723
|
+
".mneme-diary-text blockquote{margin:0 0 1lh;padding-left:12px;border-left:2px solid var(--dsw-alias-border-l2);color:var(--mneme-fg-dim)}",
|
|
3442
3724
|
".mneme-diary-text code{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:13px;background:var(--dsw-alias-interactive-bg-active);border-radius:4px;padding:1px 4px}",
|
|
3443
3725
|
".mneme-diary-sheet{margin:0;white-space:pre-wrap;word-break:break-word;font-family:inherit;font-size:12px;line-height:18px;color:var(--mneme-fg-dim)}",
|
|
3444
3726
|
".mneme-diary-empty{padding:14px 0;font-size:12px;line-height:18px;color:var(--mneme-fg-dim)}",
|
|
@@ -3453,7 +3735,7 @@ window.__ModuleLoader__.load({
|
|
|
3453
3735
|
// 刻意**不新开一份 --mneme-* 语义色层**:contrast.test.js 按首次出现的
|
|
3454
3736
|
// 「--mneme-fg-dim」定义解析色值(:118),再来一份就得保证顺序,收益却为零。
|
|
3455
3737
|
// 这一页只用第一份色层已经覆盖到的宿主 token 与既有 -dim 类名,靠形状做手绘感。
|
|
3456
|
-
".mneme-persona{display:flex;flex-direction:column;gap:10px;height:100%;min-height:0;
|
|
3738
|
+
".mneme-persona{display:flex;flex-direction:column;gap:10px;height:100%;min-height:0;box-sizing:border-box;font-size:13px;line-height:19px;color:var(--dsw-alias-label-primary)}",
|
|
3457
3739
|
".mneme-persona-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;flex:none}",
|
|
3458
3740
|
".mneme-persona-title{font-size:15px;line-height:21px;font-weight:700;letter-spacing:.3px;color:var(--dsw-alias-label-primary)}",
|
|
3459
3741
|
".mneme-persona-spacer{flex:1 1 auto;min-width:0}",
|
|
@@ -3463,10 +3745,10 @@ window.__ModuleLoader__.load({
|
|
|
3463
3745
|
// 左列身份卡:宽度固定,卡片是「贴纸」而不是菜单行。
|
|
3464
3746
|
".mneme-persona-col{flex:0 0 190px;display:flex;flex-direction:column;gap:8px;min-height:0;overflow:auto;padding-right:2px;border-right:1px dashed var(--dsw-alias-border-l2)}",
|
|
3465
3747
|
".mneme-persona-card{display:flex;align-items:center;gap:2px}",
|
|
3466
|
-
".mneme-persona-pick{flex:1 1 auto;min-width:0;text-align:left;border:1.5px solid transparent;background:0 0;cursor:pointer;font-family:inherit;font-size:
|
|
3748
|
+
".mneme-persona-pick{flex:1 1 auto;min-width:0;text-align:left;border:1.5px solid transparent;background:0 0;cursor:pointer;font-family:inherit;font-size:12px;line-height:18px;padding:5px 10px;border-radius:12px 8px 13px 9px;color:var(--mneme-fg-dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
3467
3749
|
".mneme-persona-pick:hover{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
3468
3750
|
".mneme-persona-pick:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}",
|
|
3469
|
-
".mneme-persona-pick--on{border-color:var(--
|
|
3751
|
+
".mneme-persona-pick--on{border-color:var(--mneme-accent);color:var(--dsw-alias-label-primary);font-weight:600;transform:rotate(-.3deg)}",
|
|
3470
3752
|
".mneme-persona-del{flex:none;border:0;background:0 0;cursor:pointer;font-family:inherit;font-size:13px;line-height:18px;padding:2px 6px;border-radius:8px;color:var(--mneme-fg-dim)}",
|
|
3471
3753
|
".mneme-persona-del:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--mneme-danger)}",
|
|
3472
3754
|
".mneme-persona-del:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}",
|
|
@@ -3515,7 +3797,7 @@ window.__ModuleLoader__.load({
|
|
|
3515
3797
|
// sharedialog 自己不带 padding:它和 impdialog 同挂一条 div(见 JSX 里的
|
|
3516
3798
|
// `className:"mneme-impdialog mneme-sharedialog"`),盒模型由 impdialog 带过来,
|
|
3517
3799
|
// 所以它同样 +30px;宽度取哪一条由源码顺序决定,故两条都得是 border-box。
|
|
3518
|
-
"@container (max-width: 480px){.mneme-xbar{justify-content:flex-start;padding:0 52px 0 10px}.mneme-vtab{padding:0 10px}.mneme-vtabs{min-width:0;overflow-x:auto;overflow-y:hidden;scrollbar-width:none}.mneme-vtabs::-webkit-scrollbar{height:0}.mneme-xmain{flex-direction:column}.mneme-xside{width:100%;flex:0 1 auto;min-height:0;max-height:min(42vh,300px);border-right:0;border-bottom:1px solid var(--dsw-alias-border-l2);overscroll-behavior:contain}.mneme-xbrowsebar{padding:8px 12px}.mneme-filterbar{padding:8px 12px}.mneme-drawer{width:100%}.mneme-drawerbody{padding:16px 14px 24px}.mneme-cards{padding:14px 12px 24px;overscroll-behavior:contain}.mneme-ent{flex-direction:column}.mneme-entrail{width:100%;flex:0 1 auto;min-height:0;max-height:min(40vh,280px);border-right:0;border-bottom:1px solid var(--dsw-alias-border-l2)}.mneme-entrailbody{overscroll-behavior:contain}.mneme-entdetail{padding:16px 14px 24px}.mneme-set{padding:8px 14px 40px}.mneme-status{padding:16px 14px 32px}.mneme-menu{box-sizing:border-box;min-width:0;width:min(200px,calc(100cqw - 32px))}.mneme-impdialog{box-sizing:border-box;width:min(300px,calc(100cqw - 32px))}.mneme-sharedialog{box-sizing:border-box;width:min(340px,calc(100cqw - 32px))}.mneme-graph{padding:10px 12px 12px}.mneme-graphsvg{max-height:min(320px,52vh);touch-action:pan-y}.mneme-glabel{font-size:14px}.mneme-graphside{position:static;width:auto;max-width:100%;max-height:min(200px,30vh);margin-top:8px}}",
|
|
3800
|
+
"@container (max-width: 480px){.mneme-xbar{justify-content:flex-start;padding:0 52px 0 10px}.mneme-vtab{padding:0 10px;flex:none;white-space:nowrap}.mneme-vtabs{min-width:0;overflow-x:auto;overflow-y:hidden;scrollbar-width:none}.mneme-vtabs::-webkit-scrollbar{height:0}.mneme-xmain{flex-direction:column}.mneme-xside{width:100%;flex:0 1 auto;min-height:0;max-height:min(42vh,300px);border-right:0;border-bottom:1px solid var(--dsw-alias-border-l2);overscroll-behavior:contain}.mneme-xbrowsebar{padding:8px 12px}.mneme-filterbar{padding:8px 12px}.mneme-drawer{width:100%}.mneme-drawerbody{padding:16px 14px 24px}.mneme-cards{padding:14px 12px 24px;overscroll-behavior:contain}.mneme-ent{flex-direction:column}.mneme-entrail{width:100%;flex:0 1 auto;min-height:0;max-height:min(40vh,280px);border-right:0;border-bottom:1px solid var(--dsw-alias-border-l2)}.mneme-entrailbody{overscroll-behavior:contain}.mneme-entdetail{padding:16px 14px 24px}.mneme-set{padding:8px 14px 40px}.mneme-diary-body{flex-direction:column}.mneme-diary-list{flex:none;flex-direction:row;flex-wrap:wrap;gap:var(--mneme-space-4);overflow:visible;padding:0 0 var(--mneme-space-6);border-right:0}.mneme-persona-body{flex-direction:column}.mneme-persona-col{flex:none;overflow:visible;padding:0 0 var(--mneme-space-6);border-right:0}.mneme-persona-about{max-height:none}.mneme-status{padding:16px 14px 32px}.mneme-menu{box-sizing:border-box;min-width:0;width:min(200px,calc(100cqw - 32px))}.mneme-impdialog{box-sizing:border-box;width:min(300px,calc(100cqw - 32px))}.mneme-sharedialog{box-sizing:border-box;width:min(340px,calc(100cqw - 32px))}.mneme-graph{padding:10px 12px 12px}.mneme-graphsvg{max-height:min(320px,52vh);touch-action:pan-y}.mneme-glabel{font-size:14px}.mneme-graphside{position:static;width:auto;max-width:100%;max-height:min(200px,30vh);margin-top:8px}}",
|
|
3519
3801
|
// 360 档只做两件事:隐掉 14px 的 tab 图标(.mneme-vtab 带图标时约 109px/个、
|
|
3520
3802
|
// 4 个约 436px,320px 容器原地就横向溢出),再把正文 padding 收到 12–14px。
|
|
3521
3803
|
// 页签横向滚动不在这一档、属于 480 主断点,理由见 480 档那条算式。
|
|
@@ -3531,6 +3813,117 @@ window.__ModuleLoader__.load({
|
|
|
3531
3813
|
|
|
3532
3814
|
const h = react.createElement;
|
|
3533
3815
|
|
|
3816
|
+
/* panel-geometry:begin
|
|
3817
|
+
* 几何原语层(视觉系统 P1a,docs/plans/2026-11-10-panel-visual-system.md)。
|
|
3818
|
+
* 面板此前没有组件层:每张卡片、每一行都手写 div + 行内 style,同一件事写 180 次、
|
|
3819
|
+
* 180 次各自算间距,于是 padding 长出 107 种取值。这一层只做一件事:把
|
|
3820
|
+
* 「间距/圆角/字号」收成 Token,并让调用点无法绕过它。
|
|
3821
|
+
*
|
|
3822
|
+
* 约定(写代码的人只需要记这一条):所有几何参数只收 GEOMETRY 表里的键,
|
|
3823
|
+
* 传表外的值(9、10、14、16.5、「8px」)当场抛错。宁可渲染炸掉,也不要
|
|
3824
|
+
* 悄悄画出一个第 108 种间距——后者没人会发现,前者一进测试就红。
|
|
3825
|
+
*
|
|
3826
|
+
* 标记注释不是装饰:test/panel-tokens.test.js 把 begin/end 之间这段抽出来
|
|
3827
|
+
* 直接跑(注入一个假的 createElement),所以这段必须保持纯函数、不许 require。
|
|
3828
|
+
*/
|
|
3829
|
+
const GEOMETRY = {
|
|
3830
|
+
space: [0, 2, 4, 6, 8, 10, 12, 16, 20, 24, 40],
|
|
3831
|
+
radius: [4, 8, 12, "pill"],
|
|
3832
|
+
type: [12, 13, 15],
|
|
3833
|
+
weight: [400, 600]
|
|
3834
|
+
};
|
|
3835
|
+
function geometryVar(group, key) {
|
|
3836
|
+
const table = GEOMETRY[group];
|
|
3837
|
+
if (!table) throw new Error("mneme panel: 未知刻度组 " + group);
|
|
3838
|
+
if (!table.includes(key)) {
|
|
3839
|
+
throw new Error(
|
|
3840
|
+
"mneme panel: " + group + " 不接受 " + JSON.stringify(key) +
|
|
3841
|
+
"——只收刻度 " + table.join("/") + "(见 lib/client.js 的 GEOMETRY)"
|
|
3842
|
+
);
|
|
3843
|
+
}
|
|
3844
|
+
return "var(--mneme-" + group + "-" + key + ")";
|
|
3845
|
+
}
|
|
3846
|
+
const space = (key) => geometryVar("space", key);
|
|
3847
|
+
const radius = (key) => geometryVar("radius", key);
|
|
3848
|
+
const fontOf = (key) => geometryVar("type", key);
|
|
3849
|
+
const weightOf = (key) => geometryVar("weight", key);
|
|
3850
|
+
|
|
3851
|
+
function Stack(o) {
|
|
3852
|
+
const p = o || {};
|
|
3853
|
+
const style = Object.assign(
|
|
3854
|
+
{ display: "flex", flexDirection: p.direction || "column", gap: space(p.gap == null ? 0 : p.gap), padding: space(p.pad == null ? 0 : p.pad) },
|
|
3855
|
+
p.align ? { alignItems: p.align } : null,
|
|
3856
|
+
p.justify ? { justifyContent: p.justify } : null,
|
|
3857
|
+
p.wrap ? { flexWrap: "wrap" } : null,
|
|
3858
|
+
p.grow ? { flexGrow: 1, minWidth: 0 } : null,
|
|
3859
|
+
p.style || null
|
|
3860
|
+
);
|
|
3861
|
+
return h("div", { className: p.className, style }, p.children);
|
|
3862
|
+
}
|
|
3863
|
+
function Inline(o) {
|
|
3864
|
+
const p = o || {};
|
|
3865
|
+
return Stack(Object.assign({}, p, { direction: "row", align: p.align || "center", gap: p.gap == null ? 6 : p.gap }));
|
|
3866
|
+
}
|
|
3867
|
+
// 唯一允许自己写内距的壳:卡片内距只有两档,dense 给紧凑列表用。
|
|
3868
|
+
function Card(o) {
|
|
3869
|
+
const p = o || {};
|
|
3870
|
+
const head = p.title == null && p.actions == null ? null : h(
|
|
3871
|
+
"div",
|
|
3872
|
+
{ style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: space(8), marginBottom: space(8) } },
|
|
3873
|
+
h("div", { style: { fontSize: fontOf(13), fontWeight: weightOf(600), color: "var(--dsw-alias-label-primary)", minWidth: 0 } }, p.title),
|
|
3874
|
+
p.actions == null ? null : h("div", { style: { display: "flex", alignItems: "center", gap: space(6), flex: "none" } }, p.actions)
|
|
3875
|
+
);
|
|
3876
|
+
return h(
|
|
3877
|
+
"div",
|
|
3878
|
+
{
|
|
3879
|
+
className: p.className,
|
|
3880
|
+
style: Object.assign(
|
|
3881
|
+
{
|
|
3882
|
+
boxSizing: "border-box",
|
|
3883
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
3884
|
+
borderRadius: radius(8),
|
|
3885
|
+
padding: p.dense ? space(8) : space(12),
|
|
3886
|
+
background: "transparent",
|
|
3887
|
+
minWidth: 0
|
|
3888
|
+
},
|
|
3889
|
+
p.style || null
|
|
3890
|
+
)
|
|
3891
|
+
},
|
|
3892
|
+
head,
|
|
3893
|
+
p.children
|
|
3894
|
+
);
|
|
3895
|
+
}
|
|
3896
|
+
function Section(o) {
|
|
3897
|
+
const p = o || {};
|
|
3898
|
+
return h(
|
|
3899
|
+
"section",
|
|
3900
|
+
{ style: Object.assign({ display: "flex", flexDirection: "column", gap: space(8), marginBottom: space(16) }, p.style || null) },
|
|
3901
|
+
p.title == null && p.hint == null ? null : h(
|
|
3902
|
+
"div",
|
|
3903
|
+
{ style: { display: "flex", flexDirection: "column", gap: space(2) } },
|
|
3904
|
+
p.title == null ? null : h("h3", { style: { margin: 0, fontSize: fontOf(13), fontWeight: weightOf(600), color: "var(--dsw-alias-label-primary)" } }, p.title),
|
|
3905
|
+
p.hint == null ? null : h("p", { style: { margin: 0, fontSize: fontOf(12), lineHeight: 1.5, color: "var(--mneme-fg-dim)" } }, p.hint)
|
|
3906
|
+
),
|
|
3907
|
+
p.children
|
|
3908
|
+
);
|
|
3909
|
+
}
|
|
3910
|
+
// 仪表盘栅格:auto-fill 而不是断点,容器宽度变化时自己重排。
|
|
3911
|
+
function Grid(o) {
|
|
3912
|
+
const p = o || {};
|
|
3913
|
+
return h(
|
|
3914
|
+
"div",
|
|
3915
|
+
{
|
|
3916
|
+
style: Object.assign(
|
|
3917
|
+
{ display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(" + (p.min || 220) + "px,1fr))", gap: space(p.gap == null ? 12 : p.gap) },
|
|
3918
|
+
p.style || null
|
|
3919
|
+
)
|
|
3920
|
+
},
|
|
3921
|
+
p.children
|
|
3922
|
+
);
|
|
3923
|
+
}
|
|
3924
|
+
/* panel-geometry:end */
|
|
3925
|
+
|
|
3926
|
+
|
|
3534
3927
|
// --- graph view constants ---
|
|
3535
3928
|
// Entity type → node fill. The host has no palette token for categorical
|
|
3536
3929
|
// data, so these are fixed hues tuned for both light and dark themes
|
|
@@ -3973,6 +4366,46 @@ window.__ModuleLoader__.load({
|
|
|
3973
4366
|
selected.edge.source
|
|
3974
4367
|
? t("memory.graph.source").replace("{s}", t(`memory.graph.src.${selected.edge.source}`) || selected.edge.source)
|
|
3975
4368
|
: null),
|
|
4369
|
+
// #619 可见化 B:情感边单独一行,形状与上面两行(关系时间 / 权重来源)
|
|
4370
|
+
// 区分开——它答的是「这是什么情绪、算一次还是算一类」,不是权重问题。
|
|
4371
|
+
//
|
|
4372
|
+
// 三种形态必须分得清,否则界面上会出现两种假话:
|
|
4373
|
+
// · metadata 为空 → 这是抽取器写的老边(或人工边),本来就没有情绪,
|
|
4374
|
+
// 不显示任何情绪行。显示「情绪 —」等于说「有边但情绪丢了」。
|
|
4375
|
+
// · metadata.emotion 有值、promotedToTrait !== true → 一次具体场合。
|
|
4376
|
+
// · promotedToTrait === true → 已升格为稳定倾向,必须带上跨几个独立
|
|
4377
|
+
// 场合,否则用户会把「一次情绪」和「一贯倾向」读成同一件事。
|
|
4378
|
+
(() => {
|
|
4379
|
+
const meta = selected.edge.metadata;
|
|
4380
|
+
if (!meta || typeof meta !== "object") return null;
|
|
4381
|
+
const emotion = typeof meta.emotion === "string" ? meta.emotion.trim() : "";
|
|
4382
|
+
if (!emotion) return null;
|
|
4383
|
+
const attrs = [];
|
|
4384
|
+
if (meta.promotedToTrait === true) {
|
|
4385
|
+
const n = Number(meta.traitSessionCount);
|
|
4386
|
+
attrs.push(Number.isFinite(n) && n > 0
|
|
4387
|
+
? t("memory.graph.affectiveTrait").replace("{n}", String(n))
|
|
4388
|
+
: t("memory.graph.affectiveTrait").replace("{n}", "?"));
|
|
4389
|
+
} else {
|
|
4390
|
+
// 场合档:说「这一次」。sessionCount > 1 时不能借用 trait 的文案
|
|
4391
|
+
// ——那会把这批边说成「稳定倾向」,而升格是另一回事(判据是跨
|
|
4392
|
+
// 独立会话数达到阈值并铸出 pattern,标记在 promotedToTrait 上)。
|
|
4393
|
+
const sessions = Number(meta.sessionCount);
|
|
4394
|
+
attrs.push(Number.isFinite(sessions) && sessions > 1
|
|
4395
|
+
? t("memory.graph.affectiveOccasions").replace("{n}", String(sessions))
|
|
4396
|
+
: t("memory.graph.affectiveOccasion"));
|
|
4397
|
+
}
|
|
4398
|
+
if (typeof meta.attribution === "string" && meta.attribution.trim()) {
|
|
4399
|
+
attrs.push(meta.attribution.trim());
|
|
4400
|
+
}
|
|
4401
|
+
// 证据条数用带标签的短语,不裸放一个数字——裸数字读起来像权重或 id。
|
|
4402
|
+
if (Array.isArray(meta.evidence) && meta.evidence.length > 0) {
|
|
4403
|
+
attrs.push(t("memory.graph.affectiveEvidence").replace("{n}", String(meta.evidence.length)));
|
|
4404
|
+
}
|
|
4405
|
+
return h("div", { className: "mneme-gs-meta mneme-gs-affective" },
|
|
4406
|
+
t("memory.graph.emotion").replace("{e}", emotion),
|
|
4407
|
+
attrs.length > 0 ? ` · ${attrs.join(" · ")}` : null);
|
|
4408
|
+
})(),
|
|
3976
4409
|
selected.edge.memory_id && h("button", {
|
|
3977
4410
|
className: "mneme-footbtn",
|
|
3978
4411
|
onClick: () => onJumpMemory && onJumpMemory({ id: selected.edge.memory_id })
|
|
@@ -4067,6 +4500,14 @@ window.__ModuleLoader__.load({
|
|
|
4067
4500
|
const out = e.from === data.root.id;
|
|
4068
4501
|
const other = nodeById.get(out ? e.to : e.from);
|
|
4069
4502
|
const otherName = other?.name ?? (out ? e.to : e.from);
|
|
4503
|
+
// #619 可见化 B:情感边在列表里也要认得出。只列
|
|
4504
|
+
// `affective_context` 这个类型名,用户读不出「什么情绪、算一类
|
|
4505
|
+
// 还是一次」——而这两件事正是这条边的全部信息量。非情感边不受
|
|
4506
|
+
// 影响(meta.emotion 为空),列表宽度也不变。
|
|
4507
|
+
const emo = e.metadata && typeof e.metadata === "object"
|
|
4508
|
+
? (typeof e.metadata.emotion === "string" ? e.metadata.emotion.trim() : "")
|
|
4509
|
+
: "";
|
|
4510
|
+
const isTrait = e.metadata?.promotedToTrait === true;
|
|
4070
4511
|
return h("button", {
|
|
4071
4512
|
key: e.id,
|
|
4072
4513
|
className: "mneme-relrow",
|
|
@@ -4075,6 +4516,7 @@ window.__ModuleLoader__.load({
|
|
|
4075
4516
|
},
|
|
4076
4517
|
h("span", { className: "mneme-relname", style: { textAlign: "right", flex: 1 } }, entity.name),
|
|
4077
4518
|
h("span", { className: "mneme-reltype" }, e.relation_type),
|
|
4519
|
+
emo ? h("span", { className: isTrait ? "mneme-relaffect mneme-relaffect--trait" : "mneme-relaffect" }, emo) : null,
|
|
4078
4520
|
h("span", { className: "mneme-relname", style: { flex: 1 } }, otherName)
|
|
4079
4521
|
);
|
|
4080
4522
|
}),
|
|
@@ -4173,51 +4615,73 @@ window.__ModuleLoader__.load({
|
|
|
4173
4615
|
// 「重置用户配置」,子项自己勾着的值要留着,也应该能提前设好。同一份关系在
|
|
4174
4616
|
// 后端 src/config.js 的 INJECT_CHILD_FLAGS(运行时闸门),两侧漂移由
|
|
4175
4617
|
// test/inject-parent-gate.test.js 钉住。
|
|
4176
|
-
const FEATURE_CHILDREN = {
|
|
4618
|
+
const FEATURE_CHILDREN = {
|
|
4619
|
+
autoInject: ["injectGuidanceEnabled", "continuityRescueEnabled"],
|
|
4620
|
+
// 热度衰减的注入侧乘数(#362)是它的第二口径:数据同源、含义不同
|
|
4621
|
+
// (曲线算没算 vs 排序用没用),缩进子行贴着总闸,父关时标注不生效。
|
|
4622
|
+
heatEnabled: ["injectHeatEnabled"],
|
|
4623
|
+
// 地志的两个子开关贴着总闸(#607 后续):都是「地志开着才有意义」的
|
|
4624
|
+
// 口径开关——读路径自动重跑 T0、面板按钮能否发起 T1。
|
|
4625
|
+
chorographyEnabled: ["chorographyAutoRefresh", "chorographyT1FromPanel"]
|
|
4626
|
+
};
|
|
4177
4627
|
// Issue #427 建议 1:折叠线挪到「新手之外」。group.basic 是首屏层,只列新
|
|
4178
4628
|
// 用户敢动的总开关;group.scope 也在首屏——它回答新用户的第二个必答题
|
|
4179
4629
|
// 「它记的东西会不会被别的会话看到」。其余组整组收进「全部设置」折叠
|
|
4180
4630
|
// (showAll)。成员从原组里移走而不是渲染时过滤:同一项永远只渲染一次。
|
|
4181
|
-
// 首屏可点项 = 3 总开关 + autoInject 的 2 个子开关 +
|
|
4182
|
-
// 「全部设置」按钮 =
|
|
4183
|
-
//
|
|
4184
|
-
//
|
|
4631
|
+
// 首屏可点项 = 3 总开关 + autoInject 的 2 个子开关 + 扩展功能 3 个闸门 +
|
|
4632
|
+
// scope 2 开关 + 「全部设置」按钮 = 11。v0.9.0 曾按产品主人点名把 M.O.
|
|
4633
|
+
// 独立成组并提进首屏(当时豁免 ≤8 软预算,理由记在 test/client.test.js 的
|
|
4634
|
+
// 同名用例里);v0.13.x 主人决定把 M.O. 下架,预算回到 8。
|
|
4635
|
+
// v0.13.x 第二次豁免(#607 后续):主人要「一眼看见」仓库地志与知识库这两个
|
|
4636
|
+
// 扩展子系统的总闸——此前一个埋在「全部设置 → 它什么时候写进去」的组尾、
|
|
4637
|
+
// 一个连 FEATURE_GROUPS 都没进(后端全通、面板只有文案没有勾选框),等于
|
|
4638
|
+
// 有功能没入口。为此新开 group.ext 并进首屏,预算 8→11。豁免的是「一眼看见」
|
|
4639
|
+
// 这条产品要求,不是把预算取消:再往首屏塞东西前先问它是不是和这三个闸门
|
|
4640
|
+
// 同级的子系统,不是就留在折叠层。
|
|
4185
4641
|
// RuntimeProvisionBlock 配好即消失,常态首屏更少。
|
|
4186
|
-
const FEATURE_FIRST_SCREEN = ["group.basic", "group.scope"];
|
|
4642
|
+
const FEATURE_FIRST_SCREEN = ["group.basic", "group.ext", "group.scope"];
|
|
4187
4643
|
const FEATURE_GROUPS = [
|
|
4188
4644
|
{ key: "group.basic", items: ["autoInject", "autoSummarize", "autoDream"] },
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4645
|
+
// 扩展功能(v0.13.x):mneme 本体只做记忆,仓库地志 / 知识库 / 日记本是
|
|
4646
|
+
// 三个可选子系统,各自的总闸决定「座位、工具、注入块存不存在」。它们此前
|
|
4647
|
+
// 散在 enhance 组尾(choro / diary)或压根不在 FEATURE_GROUPS 里(kb),
|
|
4648
|
+
// 结果是开了才找得到、找不到就没法开——#607 报的「知识库没成型」有一半
|
|
4649
|
+
// 是这个入口问题。单独成组并进首屏,一眼能看见。
|
|
4650
|
+
// 子块(choroSub 档位 / diarySub 定时写)不走「组尾追加」那条老路:ext 组的
|
|
4651
|
+
// 子块紧贴自己那个总闸渲染(见下方 renderGroup 的 subAfter),否则地志的档位
|
|
4652
|
+
// 下拉会隔着「知识库」「日记本」两行出现,读起来像在问知识库每轮付多少。
|
|
4653
|
+
{ key: "group.ext", items: ["chorographyEnabled", "kbEnabled", "diaryEnabled"] },
|
|
4654
|
+
// 防线与审计(#427 revamp 重排):六个「拦什么、记什么」的开关归一处——
|
|
4655
|
+
// 写入准入、注入前判定、敏感信息扫描、LLM 审计账本。它们与「每轮付多少」
|
|
4656
|
+
// 的行为开关不同级:都是防线,动任何一道都改变风险面,所以单独成组。
|
|
4657
|
+
{ key: "group.safety", items: ["writeAdmission.enabled", "writeAdmission.enforce", "preInjectGate.enabled", "preInjectGate.enforce", "sensitiveScanEnabled", "llmAudit.enabled"] },
|
|
4658
|
+
// 蒸馏与写入:写进去的东西长什么样——实体抽取(子块:路由与思考强度)、
|
|
4659
|
+
// 编码回溯、文档摘要卡、质量过滤、原文留存(子块:天数)。
|
|
4660
|
+
{ key: "group.pipeline", items: ["entityExtractionEnabled", "codingRetrospect", "documentMemoryEnabled", "memoryQualityFilter.enabled",
|
|
4661
|
+
// #399 的留存总闸放组末:distillSub(天数行)经 subAfter 紧贴它渲染。
|
|
4192
4662
|
"distillRecords.enabled"] },
|
|
4193
|
-
//
|
|
4194
|
-
//
|
|
4663
|
+
// 检索与注入:捞什么、排什么序——热记忆、时间前缀、重排、BM25、语义去重、
|
|
4664
|
+
// 热度衰减。resilientModelDownload 是本地嵌入的配套(下载/校验/adopt),
|
|
4665
|
+
// 跟着检索组走。embedProvider 下拉(无开关行的子块)挂本组组尾。
|
|
4666
|
+
{ key: "group.retrieve", items: ["hotMemoryEnabled", "injectTimePrefix", "rerankEnabled", "bm25SearchEnabled", "searchSemanticDedup", "heatEnabled", "resilientModelDownload"] },
|
|
4667
|
+
// 个性化(beta 区):人格注入总闸 + 严格寻址 + 画像草稿 + 自动事实。
|
|
4668
|
+
// 人格内容在「个性化」页的人格卡,这里只有开关。
|
|
4669
|
+
{ key: "group.persona", items: ["personaEnabled", "personaStrictAddressing", "profileExtractEnabled", "profileFactsEnabled"] },
|
|
4670
|
+
// M.O. 已下架(v0.13.x):那一组连同首屏位一起摘掉,不是「折进全部设置」。
|
|
4671
|
+
// 反转的是 v0.9.0 时产品主人「别藏在里面」的点名——主人明确要求下架,
|
|
4195
4672
|
// 所以反向操作。三个键仍在 config schema 与 settings 白名单里,座位闸门那条
|
|
4196
4673
|
// effect 也仍在(fail-closed:旗标 false 时一个座位都不注册),随时可恢复:
|
|
4197
4674
|
// 把这行加回 FEATURE_GROUPS 与 FEATURE_FIRST_SCREEN 即可。
|
|
4198
4675
|
// 面板不再渲染它,等于只剩配置文件一条入口——这是「下架」的本意。
|
|
4199
|
-
{ key: "group.enhance", items: ["entityExtractionEnabled", "codingRetrospect", "rerankEnabled", "resilientModelDownload", "searchSemanticDedup", "bm25SearchEnabled", "heatEnabled", "documentMemoryEnabled",
|
|
4200
|
-
// 人格定义页(beta):注入总闸。分组随 enhance(行为开关,非座位闸门);
|
|
4201
|
-
// 人格内容在设置页「个性化」区的人格卡。
|
|
4202
|
-
// 严格寻址是同一条链上的第二个口径开关(只在总闸打开时起作用)。
|
|
4203
|
-
"personaEnabled", "personaStrictAddressing", "profileExtractEnabled",
|
|
4204
|
-
// 自动事实(profile_facts):dream 后的自动维护闸。展示与人工修正不受它管
|
|
4205
|
-
// (GET/apply 在 flag 关时照常放行,存量事实要能看能改),它只决定「自动做梦
|
|
4206
|
-
// 维护」与面板「立即更新」这两条 LLM 路径开不开。
|
|
4207
|
-
"profileFactsEnabled",
|
|
4208
|
-
// chorography(仓库地志)的暴露闸(M1 就有的键,M3 才上 UI):它是「是否
|
|
4209
|
-
// 启用这个子系统」而不是「每轮付多少」,所以和上面这些行为开关同组;
|
|
4210
|
-
// 每轮付出多少由紧随组尾的 choroSub 档位管。放组末是因为子块按组尾渲染
|
|
4211
|
-
// ——档位下拉要贴着它的总闸。
|
|
4212
|
-
"chorographyEnabled",
|
|
4213
|
-
// 日记本(v0.13):右栏座位的闸门。和 M.O. 一样是「开关=座位注册与否」,
|
|
4214
|
-
// 但它属于「它在替你写字」这一类行为开关,所以归 enhance 而不是单开一组。
|
|
4215
|
-
"diaryEnabled"] },
|
|
4216
4676
|
{ key: "group.dream", items: ["sleepModeEnabled", "sleepAffectiveEnabled"] },
|
|
4217
4677
|
// v0.8.0 A4(issue #17):作用域隔离组——标注总开关 + 严格硬过滤。
|
|
4218
4678
|
{ key: "group.scope", items: ["scopeEnabled", "strictScope"] }
|
|
4219
4679
|
];
|
|
4220
|
-
|
|
4680
|
+
// 第二层折叠(「高级」):更少用的开关。实体召回(#219)与图谱锚定(#24)
|
|
4681
|
+
// 也归这里——默认关、依赖实体抽取产出(pipeline 组),且是进阶检索行为,
|
|
4682
|
+
// 与 retrieve 的常开旋钮不同级。调优类数值键(seedCap/depth/weight* 等)
|
|
4683
|
+
// 刻意不设座位,走配置文件(见 FEATURE_NUMBERS 的同名纪律)。
|
|
4684
|
+
const FEATURE_ADVANCED_BOOLS = ["hybridInject", "selectiveInjectEnabled", "adaptiveThresholdEnabled", "entityRecallEnabled", "graphAnchoringEnabled", "reflectionUpdateEnabled", "reflectionFailureTracking", "conflictFreezeEnabled", "trustEpistemicWeighting"];
|
|
4221
4685
|
// 字符串键(blur/Enter 提交,空串合法 = 跟随默认):巩固模型与语义
|
|
4222
4686
|
// 检索路线。embedProvider 是枚举,用下拉单独渲染。
|
|
4223
4687
|
const FEATURE_STRINGS = ["dreamProvider", "dreamModel", "sleepProvider", "sleepModel", "entityExtractionProvider", "entityExtractionModel", "localEmbedModel", "ollamaBaseUrl", "ollamaModel",
|
|
@@ -4395,6 +4859,9 @@ window.__ModuleLoader__.load({
|
|
|
4395
4859
|
// 冻结值并给出 pendingRestart 名单;本面板只负责把它显示出来。
|
|
4396
4860
|
const pending = new Set((state && state.pendingRestart) || []);
|
|
4397
4861
|
const flagTag = (k) => {
|
|
4862
|
+
// 徽标只报「需要注意」的两态(#427 建议 3 的收窄,2026-11-11 产品口径):
|
|
4863
|
+
// 「默认」二字挂满 31 行、几乎永远不变,把真正要看的「已改 / 待重启」
|
|
4864
|
+
// 淹掉了。默认态不再渲染徽标——恢复出厂这件事由开关位置本身表达。
|
|
4398
4865
|
if (pending.has(k)) {
|
|
4399
4866
|
return {
|
|
4400
4867
|
kind: "mneme-badge--pending",
|
|
@@ -4402,10 +4869,11 @@ window.__ModuleLoader__.load({
|
|
|
4402
4869
|
title: t("memory.features.tagPending.hint")
|
|
4403
4870
|
};
|
|
4404
4871
|
}
|
|
4872
|
+
if (!Object.prototype.hasOwnProperty.call(ovr, k)) return null;
|
|
4405
4873
|
return {
|
|
4406
|
-
kind:
|
|
4407
|
-
text: t(
|
|
4408
|
-
title: t(
|
|
4874
|
+
kind: "mneme-badge--changed",
|
|
4875
|
+
text: t("memory.features.tagChanged"),
|
|
4876
|
+
title: t("memory.features.tagChanged.hint")
|
|
4409
4877
|
};
|
|
4410
4878
|
};
|
|
4411
4879
|
const boolRow = (k, opts = {}) => h(FeatureRow, {
|
|
@@ -4684,6 +5152,14 @@ window.__ModuleLoader__.load({
|
|
|
4684
5152
|
h("div", { className: "mneme-featsubhint" }, t("memory.features.diaryWriteEveryMinutes.hint"))
|
|
4685
5153
|
);
|
|
4686
5154
|
|
|
5155
|
+
// Issue #637:注入轮换窗口(0 = 关)。挂在 autoInject 下(它是注入姿态,
|
|
5156
|
+
// 总闸关掉就没有「每轮带什么」可谈),随 group.basic 首屏渲染。键此前不在
|
|
5157
|
+
// 后端白名单,用户只能写宿主 patch 文件——面板上连它存在都看不见。
|
|
5158
|
+
const rotationSub = eff.autoInject && h("div", { className: "mneme-featsub" },
|
|
5159
|
+
numRow("injectRotationTurns"),
|
|
5160
|
+
h("div", { className: "mneme-featsubhint" }, t("memory.features.injectRotationTurns.hint"))
|
|
5161
|
+
);
|
|
5162
|
+
|
|
4687
5163
|
const choroSub = eff.chorographyEnabled && h("div", { className: "mneme-featsub" },
|
|
4688
5164
|
h("div", { className: "mneme-featnum" },
|
|
4689
5165
|
h("span", { className: "mneme-featnumlabel" }, t("memory.features.chorographyInject")),
|
|
@@ -4732,13 +5208,31 @@ window.__ModuleLoader__.load({
|
|
|
4732
5208
|
// 哪里」。renderGroup 是唯一的组渲染路径(含组尾子块),首屏层与
|
|
4733
5209
|
// 「全部设置」共用它,保证 g.items.map(flagRow) 只写一次、子项
|
|
4734
5210
|
// 只可能经 flagRow 渲染(test/inject-parent-gate.test.js 的门)。
|
|
5211
|
+
// 扩展组例外:它的子块必须紧贴**自己那个**总闸,不能攒到组尾。
|
|
5212
|
+
// 攒到组尾时,仓库地志的档位下拉会隔着「知识库」「日记本」两行出现,
|
|
5213
|
+
// 读起来像在问知识库每轮付多少。别的组没有这个问题(core/dream 的
|
|
5214
|
+
// 子块本来就只挂在组里最后一行那个总闸上)。
|
|
5215
|
+
const subAfter = {
|
|
5216
|
+
chorographyEnabled: choroSub,
|
|
5217
|
+
diaryEnabled: diarySub,
|
|
5218
|
+
// 子块一律紧贴自己的总闸(#638 在 ext 组确立的例外推广为全局规则):
|
|
5219
|
+
// 此前 distillSub 挂 core 组尾、entitySub 挂 enhance 组尾、
|
|
5220
|
+
// summarizeSub 挂 core 组尾而它的开关 autoSummarize 在 basic 组——
|
|
5221
|
+
// 子块与开关隔着几行甚至隔着另一个组,读起来像在给别的东西调参数。
|
|
5222
|
+
"distillRecords.enabled": distillSub,
|
|
5223
|
+
entityExtractionEnabled: entitySub,
|
|
5224
|
+
autoSummarize: summarizeSub,
|
|
5225
|
+
// #637:注入轮换窗口挂在自己的总闸 autoInject 上(#638 确立的
|
|
5226
|
+
// 「子块紧贴总闸」规则),不攒到 basic 组尾——那会让它和
|
|
5227
|
+
// autoSummarize/autoDream 挤在一起,看不出是谁的调参。
|
|
5228
|
+
autoInject: rotationSub
|
|
5229
|
+
};
|
|
4735
5230
|
const renderGroup = (g) => h(react.Fragment, { key: g.key },
|
|
4736
5231
|
h("div", { className: "mneme-featgroup" }, t(`memory.features.${g.key}`)),
|
|
4737
|
-
g.items.map(flagRow),
|
|
4738
|
-
//
|
|
4739
|
-
//
|
|
4740
|
-
g.key === "group.
|
|
4741
|
-
g.key === "group.enhance" && h(react.Fragment, null, embedSub, entitySub, diarySub, choroSub),
|
|
5232
|
+
g.items.map((k) => h(react.Fragment, { key: k }, flagRow(k), subAfter[k])),
|
|
5233
|
+
// 没有对应开关行的子块仍走组尾:embedSub 是 embedProvider 的下拉
|
|
5234
|
+
// (枚举键、没有布尔行可贴),dream/sleep 的调参子块组尾即邻位。
|
|
5235
|
+
g.key === "group.retrieve" && h(react.Fragment, null, embedSub),
|
|
4742
5236
|
g.key === "group.dream" && h(react.Fragment, null, dreamSub, sleepSub)
|
|
4743
5237
|
);
|
|
4744
5238
|
const firstScreen = FEATURE_GROUPS.filter((g) => FEATURE_FIRST_SCREEN.includes(g.key));
|
|
@@ -4770,7 +5264,26 @@ window.__ModuleLoader__.load({
|
|
|
4770
5264
|
h(Icon, { name: showAdv ? "chevronDown" : "chevronRight", size: 12 }),
|
|
4771
5265
|
t("memory.features.advancedToggle"))
|
|
4772
5266
|
),
|
|
4773
|
-
showAdv &&
|
|
5267
|
+
showAdv && h(react.Fragment, null,
|
|
5268
|
+
FEATURE_ADVANCED_BOOLS.map((k) => boolRow(k)),
|
|
5269
|
+
// conflictLedgerMode(#641):三档枚举,紧挨 conflictFreezeEnabled
|
|
5270
|
+
// 摆——两者语义关联(enforce 与 freeze 互斥,同开时冻结让位),
|
|
5271
|
+
// 隔开摆放会让人以为各自独立。枚举键没有通用渲染器,手写下拉
|
|
5272
|
+
// (choroSub 同款)。
|
|
5273
|
+
h("div", { className: "mneme-featsub" },
|
|
5274
|
+
h("div", { className: "mneme-featnum" },
|
|
5275
|
+
h("span", { className: "mneme-featnumlabel" }, t("memory.features.conflictLedgerMode")),
|
|
5276
|
+
h("select", {
|
|
5277
|
+
className: "mneme-select",
|
|
5278
|
+
value: eff.conflictLedgerMode || "off",
|
|
5279
|
+
disabled: busy,
|
|
5280
|
+
onChange: (e) => put({ conflictLedgerMode: e.target.value })
|
|
5281
|
+
},
|
|
5282
|
+
["off", "shadow", "enforce"].map((v) => h("option", { key: v, value: v }, t(`memory.features.conflictLedgerMode.${v}`))))
|
|
5283
|
+
),
|
|
5284
|
+
h("div", { className: "mneme-featsubhint" }, t("memory.features.conflictLedgerMode.hint"))
|
|
5285
|
+
)
|
|
5286
|
+
)
|
|
4774
5287
|
)
|
|
4775
5288
|
);
|
|
4776
5289
|
})()
|
|
@@ -6405,6 +6918,40 @@ window.__ModuleLoader__.load({
|
|
|
6405
6918
|
if (n >= 1e3) return `${Math.round(n / 1e3)}K`;
|
|
6406
6919
|
return String(n);
|
|
6407
6920
|
};
|
|
6921
|
+
/* panel-chart-scale:begin
|
|
6922
|
+
7 天柱状的纵轴(2026-11 修)。原实现两处错:标签写死 (v/1e6).toFixed(0)+"M"
|
|
6923
|
+
——750K / 500K / 250K 全变 1M,小库永远看到「1M / 1M / 1M / 0M」;上限又被
|
|
6924
|
+
1e6 的地板钉死,日用量 7 万时柱子只有 7% 高。等于用量越大越正常,反了。
|
|
6925
|
+
这里按 1 / 2 / 2.5 / 5 × 10^n 挑步长,四格刚好盖住峰值、不浪费超过一半高度;
|
|
6926
|
+
标签按 K / M 分档,整百万不带小数(toFixed(1) 会写成「1.0M」)。抽成纯函数并
|
|
6927
|
+
留标记,测试用 new Function 直接跑(同 panel-geometry 的先例)。 */
|
|
6928
|
+
function chartScale(dayMax) {
|
|
6929
|
+
const d = Number(dayMax);
|
|
6930
|
+
// 全零 / 未加载 → null:调用方画空态或骨架,不画一条假轴。
|
|
6931
|
+
if (!Number.isFinite(d) || d <= 0) return null;
|
|
6932
|
+
const exp = Math.pow(10, Math.floor(Math.log10(d / 4)));
|
|
6933
|
+
// 只收整数步长:2.5×10^0 = 2.5 会把整 token 轴切成小数(exp >= 10 时仍是整数)。
|
|
6934
|
+
const steps = [1, 2, 2.5, 5, 10].map((m) => m * exp).filter((s) => Number.isInteger(s));
|
|
6935
|
+
let step = steps[steps.length - 1];
|
|
6936
|
+
for (const s of steps) {
|
|
6937
|
+
if (s * 4 >= d) { step = s; break; }
|
|
6938
|
+
}
|
|
6939
|
+
return { step, max: step * 4, labels: [4, 3, 2, 1].map((k) => k * step) };
|
|
6940
|
+
}
|
|
6941
|
+
function chartAxisLabel(n) {
|
|
6942
|
+
const v = Number(n);
|
|
6943
|
+
if (!Number.isFinite(v)) return "";
|
|
6944
|
+
if (Math.abs(v) >= 1e6) {
|
|
6945
|
+
const m = v / 1e6;
|
|
6946
|
+
return m % 1 === 0 ? `${m}M` : `${m.toFixed(1)}M`;
|
|
6947
|
+
}
|
|
6948
|
+
if (Math.abs(v) >= 1e3) {
|
|
6949
|
+
const k = v / 1e3;
|
|
6950
|
+
return k % 1 === 0 ? `${k}K` : `${k.toFixed(1)}K`;
|
|
6951
|
+
}
|
|
6952
|
+
return String(v);
|
|
6953
|
+
}
|
|
6954
|
+
/* panel-chart-scale:end */
|
|
6408
6955
|
// 热力分档:固定阈值(75万/150万/250万)在低用量库里整片落在 l1,档位失真
|
|
6409
6956
|
// (复验第三轮「颜色不怎么明显」)。改按 56 天窗口内的非零日做三分位切四档
|
|
6410
6957
|
// ——GitHub 贡献图同思路,档次随库的实际用量自适应拉开;全零/无数据退化
|
|
@@ -6537,9 +7084,10 @@ window.__ModuleLoader__.load({
|
|
|
6537
7084
|
return { day: key, total: rawDayMap.get(key) ?? 0 };
|
|
6538
7085
|
})
|
|
6539
7086
|
: [];
|
|
6540
|
-
const dayMax = Math.max(
|
|
6541
|
-
|
|
6542
|
-
|
|
7087
|
+
const dayMax = Math.max(0, ...days.map((d) => d.total));
|
|
7088
|
+
// scale 为 null 有两种来源:还没加载(rawDays 为 null,画骨架)与已知为空
|
|
7089
|
+
// (七天全零,画空态文案)。两者不能混——加载中先说「没有记录」就是撒谎。
|
|
7090
|
+
const scale = rawDays ? chartScale(dayMax) : null;
|
|
6543
7091
|
// 8 周热力:数据必须来自 56 天窗口(heatStats),右缘对齐该窗口最后一天
|
|
6544
7092
|
// (UTC),左侧补齐到周一;数据窗口之外的格子隐藏留形。日期一律走 UTC——
|
|
6545
7093
|
// by_day 的 day 就是 UTC 日期。用 heatStats 而非 stats:否则 56 格里
|
|
@@ -6698,23 +7246,33 @@ window.__ModuleLoader__.load({
|
|
|
6698
7246
|
)
|
|
6699
7247
|
),
|
|
6700
7248
|
h("h3", { className: "mneme-xcolhead" }, t("memory.status.token.days")),
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
h("div", {
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
7249
|
+
statsError
|
|
7250
|
+
? h("div", { className: "mneme-statuscap", style: { color: "var(--mneme-danger)" } }, t("memory.status.error"))
|
|
7251
|
+
: rawDays === null
|
|
7252
|
+
// 未加载:留空骨架,不画柱也不刷文案(stats 与 statsError 都不作数时)。
|
|
7253
|
+
? h("div", { className: "mneme-bars" }, h("div", { className: "mneme-barcols" }))
|
|
7254
|
+
: scale
|
|
7255
|
+
? h("div", { className: "mneme-bars" },
|
|
7256
|
+
scale.labels.map((v, i) => h("div", { key: v, className: "mneme-gridline", style: { top: `${i * 25}%` } },
|
|
7257
|
+
h("span", { className: "mneme-gridlabel" }, chartAxisLabel(v))
|
|
7258
|
+
)),
|
|
7259
|
+
h("div", { className: "mneme-barcols" },
|
|
7260
|
+
days.map((d) => h("div", { className: "mneme-barcol", key: d.day },
|
|
7261
|
+
h("div", {
|
|
7262
|
+
className: "mneme-bar",
|
|
7263
|
+
style: { height: `${Math.max((d.total / scale.max) * 100, 0.4)}%` },
|
|
7264
|
+
title: `${d.day} · ${d.total.toLocaleString()}`
|
|
7265
|
+
})
|
|
7266
|
+
))
|
|
7267
|
+
)
|
|
7268
|
+
)
|
|
7269
|
+
// 已知为空(rawDays 有值、七天全零):明确说没有,不留七根贴地柱。
|
|
7270
|
+
: h("div", { className: "mneme-statuscap", style: { marginTop: "12px" } }, t("memory.status.token.daysEmpty")),
|
|
7271
|
+
scale && !statsError
|
|
7272
|
+
? h("div", { className: "mneme-barlabels" },
|
|
7273
|
+
days.map((d) => h("span", { key: d.day }, d.day.slice(5)))
|
|
7274
|
+
)
|
|
7275
|
+
: null,
|
|
6718
7276
|
// 热力与沉淀并排两列:热力只有约 745px,右侧原本整块空着。两列
|
|
6719
7277
|
// 在窄容器里回落单列(见 .mneme-flowrow 的 @container)。
|
|
6720
7278
|
h("div", { className: "mneme-flowrow" },
|
|
@@ -6802,7 +7360,7 @@ window.__ModuleLoader__.load({
|
|
|
6802
7360
|
h("div", { className: "mneme-statusnum", style: { fontSize: "16px", lineHeight: "24px" } },
|
|
6803
7361
|
t("memory.status.skippedTitle").replace("{n}", String(total))),
|
|
6804
7362
|
h("div", { className: "mneme-statuscap", style: { marginBottom: "6px" } }, t("memory.status.skippedHint")),
|
|
6805
|
-
h("ul", { style: { margin: 0, paddingLeft:
|
|
7363
|
+
h("ul", { style: { margin: 0, paddingLeft: space(16), fontSize: "12px", lineHeight: "18px" } },
|
|
6806
7364
|
list.slice(0, 4).map((bucket, i) =>
|
|
6807
7365
|
h("li", { key: i, style: { color: "var(--mneme-fg-dim)" } },
|
|
6808
7366
|
(bucket.action ? bucket.action + " · " : "") + bucket.reason + " ×" + String(bucket.count)))),
|
|
@@ -6958,10 +7516,10 @@ window.__ModuleLoader__.load({
|
|
|
6958
7516
|
h("span", null, r.label),
|
|
6959
7517
|
h("span", { className: "mneme-statrowval", title: String(r.value) }, String(r.value))))),
|
|
6960
7518
|
foot ? h("div", { className: "mneme-statfoot" }, foot) : null,
|
|
6961
|
-
h("div", { style: { marginTop: 10 } },
|
|
7519
|
+
h("div", { style: { marginTop: space(10) } },
|
|
6962
7520
|
(snap.entries || []).length === 0
|
|
6963
7521
|
? h("div", { className: "mneme-statuscap" }, t("memory.status.injectPreview.empty"))
|
|
6964
|
-
: (snap.entries || []).map((m) => h("div", { key: m.id, style: { display: "flex", gap: 8, alignItems: "baseline", fontSize: 12, padding: "
|
|
7522
|
+
: (snap.entries || []).map((m) => h("div", { key: m.id, style: { display: "flex", gap: space(8), alignItems: "baseline", fontSize: 12, padding: space(2) + " 0" } },
|
|
6965
7523
|
h("span", { className: "mneme-xcolhead" }, typeLabel(t, m.type)),
|
|
6966
7524
|
h("span", { style: { flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, m.title || "—"),
|
|
6967
7525
|
h("span", { className: "mneme-xcolhead" },
|
|
@@ -7165,6 +7723,44 @@ window.__ModuleLoader__.load({
|
|
|
7165
7723
|
}));
|
|
7166
7724
|
}
|
|
7167
7725
|
|
|
7726
|
+
// --- 矛盾分诊账本(#641,状态页只读卡)----------------------------------
|
|
7727
|
+
// 与上面的冲突队列互补:队列是「等人来」的待办(freeze 模式),账本是
|
|
7728
|
+
// 「机器观察/处置过什么」的只读面(shadow 记账、enforce 处置)。行不是
|
|
7729
|
+
// 待办——处置由 enforce 档自动完成,这里只回答「账本里有什么、分布如何」。
|
|
7730
|
+
// 空账本整块不渲染(与冲突队列同款:不打扰无矛盾实例)。
|
|
7731
|
+
function ConflictLedger({ t }) {
|
|
7732
|
+
const [data, setData] = useState(null);
|
|
7733
|
+
const [loadError, setLoadError] = useState(false);
|
|
7734
|
+
const load = () => {
|
|
7735
|
+
apiFetch("/api/dsh-mneme/conflicts/ledger?limit=50")
|
|
7736
|
+
.then((res) => { if (!res.ok) throw new Error("http"); return res.json(); })
|
|
7737
|
+
.then((d) => { setData(d); setLoadError(false); })
|
|
7738
|
+
.catch(() => { setData(null); setLoadError(true); });
|
|
7739
|
+
};
|
|
7740
|
+
useEffect(() => { load(); }, []);
|
|
7741
|
+
if (loadError) {
|
|
7742
|
+
return h("div", { className: "mneme-statuscard mneme-set-hint" }, t("memory.status.ledger.loadError"));
|
|
7743
|
+
}
|
|
7744
|
+
if (!data) return null;
|
|
7745
|
+
const counts = data.counts || {};
|
|
7746
|
+
const total = typeof data.total === "number" ? data.total : 0;
|
|
7747
|
+
if (total === 0) return null;
|
|
7748
|
+
const tierKey = (v) => "memory.status.ledger.tier." + v;
|
|
7749
|
+
const tiers = [["dup"], ["cross-scope-dup"], ["evolution"], ["conflict"]];
|
|
7750
|
+
const rows = (data.items || []).slice(0, 8);
|
|
7751
|
+
return h("div", { className: "mneme-statuscard" },
|
|
7752
|
+
h("div", { className: "mneme-set-h3" }, t("memory.status.ledger.title")),
|
|
7753
|
+
h("div", { className: "mneme-set-hint" },
|
|
7754
|
+
t("memory.status.ledger.dist")
|
|
7755
|
+
+ " " + tiers.map(([k]) => `${t(tierKey(k))} ${counts[k] ?? 0}`).join(" · ")
|
|
7756
|
+
+ " · " + t("memory.status.ledger.total") + " " + total),
|
|
7757
|
+
h("div", { className: "mneme-conflictq" },
|
|
7758
|
+
rows.map((r) => h("div", { key: r.memory_a + "|" + r.memory_b, className: "mneme-conflict-item" },
|
|
7759
|
+
h("div", { className: "mneme-conflict-reason" },
|
|
7760
|
+
`${t(tierKey(r.tier))} · ${t("memory.status.ledger.status")}: ${r.status} · ${String(r.first_seen_at || "").slice(0, 10)}`))))
|
|
7761
|
+
);
|
|
7762
|
+
}
|
|
7763
|
+
|
|
7168
7764
|
// --- 状态页工作台:让用户切实看见插件在干活 ---
|
|
7169
7765
|
// 活动流合并 llm_audit 里 autoDream/autoSummarize 的后台调用(状态、
|
|
7170
7766
|
// token、沉淀条数);沉淀记忆列表用 audit 行携带的 related_memory_ids
|
|
@@ -7361,7 +7957,7 @@ window.__ModuleLoader__.load({
|
|
|
7361
7957
|
}))),
|
|
7362
7958
|
h("div", { className: "mneme-statrows" },
|
|
7363
7959
|
tiers.map(([tier, n, label]) => h("div", { className: "mneme-statrow", key: tier },
|
|
7364
|
-
h("span", { style: { display: "flex", alignItems: "center", gap: 6 } },
|
|
7960
|
+
h("span", { style: { display: "flex", alignItems: "center", gap: space(6) } },
|
|
7365
7961
|
h("span", { className: `mneme-heatdot mneme-heatdot${tier}`, "aria-hidden": "true" }),
|
|
7366
7962
|
label),
|
|
7367
7963
|
h("span", { className: "mneme-statrowval" }, String(n))))),
|
|
@@ -7446,6 +8042,102 @@ window.__ModuleLoader__.load({
|
|
|
7446
8042
|
});
|
|
7447
8043
|
}
|
|
7448
8044
|
|
|
8045
|
+
// #619 可见化 C:情绪归因一览。
|
|
8046
|
+
// 为什么单开一栏而不是塞进实体页:实体页是「从这个人往外看」,而这里要回答的是
|
|
8047
|
+
// 「这个库一共记住了我哪些情绪」——反向、全局的一眼。塞进实体页得先选一个实体,
|
|
8048
|
+
// 用户心里还没那个人。
|
|
8049
|
+
//
|
|
8050
|
+
// 三种状态严格分开,别让任何一种冒充另一种:
|
|
8051
|
+
// loading 还没回来
|
|
8052
|
+
// error 请求失败(500 / 网络),面板明说「不等于没有记录」
|
|
8053
|
+
// empty 真的一条都没有,并且说清要怎么才会有
|
|
8054
|
+
// 这三件事在用户眼里完全不同,混成一个空列表是最容易犯也最伤人的糊弄。
|
|
8055
|
+
function AffectivePanel({ t }) {
|
|
8056
|
+
const [state, setState] = useState({ loading: true, error: false, items: [], totalEdges: 0, skippedEdges: 0, truncated: false });
|
|
8057
|
+
useEffect(() => {
|
|
8058
|
+
let cancelled = false;
|
|
8059
|
+
apiFetch("/api/dsh-mneme/semantic/graph/affective?limit=200")
|
|
8060
|
+
.then((res) => {
|
|
8061
|
+
if (!res.ok) throw new Error(`http ${res.status}`);
|
|
8062
|
+
return res.json();
|
|
8063
|
+
})
|
|
8064
|
+
.then((j) => {
|
|
8065
|
+
if (cancelled) return;
|
|
8066
|
+
setState({
|
|
8067
|
+
loading: false,
|
|
8068
|
+
error: false,
|
|
8069
|
+
items: Array.isArray(j.items) ? j.items : [],
|
|
8070
|
+
totalEdges: j.totalEdges || 0,
|
|
8071
|
+
skippedEdges: j.skippedEdges || 0,
|
|
8072
|
+
truncated: j.truncated === true
|
|
8073
|
+
});
|
|
8074
|
+
})
|
|
8075
|
+
.catch(() => {
|
|
8076
|
+
// 失败就报失败。这里绝不能退化成空 items —— 那会让「上游挂了」
|
|
8077
|
+
// 长得和「你还没记过情绪」一模一样,用户会以为功能没用。
|
|
8078
|
+
if (!cancelled) setState({ loading: true, error: true, items: [], totalEdges: 0, skippedEdges: 0, truncated: false });
|
|
8079
|
+
});
|
|
8080
|
+
return () => { cancelled = true; };
|
|
8081
|
+
}, []);
|
|
8082
|
+
|
|
8083
|
+
if (state.loading && !state.error) {
|
|
8084
|
+
return h("div", { className: "mneme-set" },
|
|
8085
|
+
h("div", { className: "mneme-set-inner" }, h("div", { className: "mneme-affective-empty", role: "status" }, t("memory.affective.loading"))));
|
|
8086
|
+
}
|
|
8087
|
+
if (state.error) {
|
|
8088
|
+
// role=alert:这是需要用户知道的一次失败,不是背景噪声。
|
|
8089
|
+
return h("div", { className: "mneme-set" },
|
|
8090
|
+
h("div", { className: "mneme-set-inner" },
|
|
8091
|
+
h("div", { className: "mneme-affective-empty mneme-affective-empty--error", role: "alert" }, t("memory.affective.error"))));
|
|
8092
|
+
}
|
|
8093
|
+
|
|
8094
|
+
const rows = state.items.map((it, i) => {
|
|
8095
|
+
const isTrait = it.attribution === "trait";
|
|
8096
|
+
const bits = [];
|
|
8097
|
+
if (isTrait) {
|
|
8098
|
+
const n = it.traitSessionCount ?? it.sessionCount;
|
|
8099
|
+
if (Number.isFinite(n)) bits.push(t("memory.affective.sessions").replace("{n}", String(n)));
|
|
8100
|
+
} else if (Number.isFinite(it.sessionCount) && it.sessionCount > 1) {
|
|
8101
|
+
bits.push(t("memory.affective.sessions").replace("{n}", String(it.sessionCount)));
|
|
8102
|
+
}
|
|
8103
|
+
if (Number.isFinite(it.evidenceCount) && it.evidenceCount > 0) {
|
|
8104
|
+
bits.push(t("memory.affective.evidence").replace("{n}", String(it.evidenceCount)));
|
|
8105
|
+
}
|
|
8106
|
+
if (it.lastSeen) bits.push(t("memory.affective.lastSeen").replace("{time}", formatRelativeTime(it.lastSeen)));
|
|
8107
|
+
return h("div", { className: isTrait ? "mneme-affective-row mneme-affective-row--trait" : "mneme-affective-row", key: `${it.personId}-${it.targetId}-${it.emotion}-${it.attribution}-${i}` },
|
|
8108
|
+
h("div", { className: "mneme-affective-main" },
|
|
8109
|
+
// 谁:服务端一直带着 person(from_name),此前前端只渲染了「对什么 ·
|
|
8110
|
+
// 什么情绪」,一条「不安」看不出是谁对谁。名字缺失时给占位符,不是
|
|
8111
|
+
// 让 undefined 直接印在界面上。
|
|
8112
|
+
h("span", { className: "mneme-affective-person" }, it.person ?? "?"),
|
|
8113
|
+
h("span", { className: "mneme-affective-arrow" }, "·"),
|
|
8114
|
+
h("span", { className: "mneme-affective-emotion" }, it.emotion),
|
|
8115
|
+
h("span", { className: "mneme-affective-arrow" }, "·"),
|
|
8116
|
+
h("span", { className: "mneme-affective-target" }, it.target ?? "?"),
|
|
8117
|
+
h("span", { className: isTrait ? "mneme-affective-tag mneme-affective-tag--trait" : "mneme-affective-tag" },
|
|
8118
|
+
isTrait ? t("memory.affective.trait") : t("memory.affective.occasion"))
|
|
8119
|
+
),
|
|
8120
|
+
bits.length ? h("div", { className: "mneme-affective-meta" }, bits.join(" · ")) : null
|
|
8121
|
+
);
|
|
8122
|
+
});
|
|
8123
|
+
|
|
8124
|
+
return h("div", { className: "mneme-set" },
|
|
8125
|
+
h("div", { className: "mneme-set-inner" },
|
|
8126
|
+
h("div", { className: "mneme-affective-head" },
|
|
8127
|
+
h("div", { className: "mneme-affective-title" }, t("memory.affective.title")),
|
|
8128
|
+
h("div", { className: "mneme-affective-sub" }, t("memory.affective.sub"))
|
|
8129
|
+
),
|
|
8130
|
+
state.items.length === 0
|
|
8131
|
+
? h("div", { className: "mneme-affective-empty", role: "status" }, t("memory.affective.empty"))
|
|
8132
|
+
: h("div", { className: "mneme-affective-list" }, rows),
|
|
8133
|
+
// 截断与跳过都要说出来:读者看到「就这些」时,得知道是自己没记够,
|
|
8134
|
+
// 还是这一页只放了这么多。
|
|
8135
|
+
state.truncated ? h("div", { className: "mneme-affective-note" }, t("memory.affective.truncated").replace("{n}", String(state.totalEdges))) : null,
|
|
8136
|
+
state.skippedEdges > 0 ? h("div", { className: "mneme-affective-note" }, t("memory.affective.skipped").replace("{n}", String(state.skippedEdges))) : null
|
|
8137
|
+
)
|
|
8138
|
+
);
|
|
8139
|
+
}
|
|
8140
|
+
|
|
7449
8141
|
function StatusPanel({ t, onBrowse }) {
|
|
7450
8142
|
// #178:冲突卡键盘直达——激活后把焦点与视口带到队列块。
|
|
7451
8143
|
const queueRef = useRef(null);
|
|
@@ -7492,6 +8184,7 @@ window.__ModuleLoader__.load({
|
|
|
7492
8184
|
h("div", { className: "mneme-statushead" }, t("memory.status.sec.maintenance")),
|
|
7493
8185
|
h("div", { className: "mneme-statusgrid" }, h(MaintenanceCard, { t })),
|
|
7494
8186
|
h("div", { ref: queueRef }, h(ConflictsQueue, { t })),
|
|
8187
|
+
h(ConflictLedger, { t }),
|
|
7495
8188
|
h(WorkbenchSection, { t, onBrowse })
|
|
7496
8189
|
);
|
|
7497
8190
|
}
|
|
@@ -8889,12 +9582,27 @@ window.__ModuleLoader__.load({
|
|
|
8889
9582
|
setView("memory");
|
|
8890
9583
|
};
|
|
8891
9584
|
|
|
9585
|
+
// 页签 key -> 图标名。原先是一串嵌套三元,新增页签只能落到最后的 else
|
|
9586
|
+
// 分支上、和设置页共用齿轮(情绪页与日记页就是这么哑掉的)。表放在
|
|
9587
|
+
// ICON_PATHS 之外、与 subviews 同处,新增页签时两个数组一左一右,漏登记
|
|
9588
|
+
// 一眼能看出来;test/client.test.js 的图标锁也按这张表核对注册表。
|
|
9589
|
+
const TAB_ICONS = {
|
|
9590
|
+
memory: "database",
|
|
9591
|
+
entity: "waypoints",
|
|
9592
|
+
status: "activity",
|
|
9593
|
+
settings: "settings",
|
|
9594
|
+
persona: "persona",
|
|
9595
|
+
affective: "heart",
|
|
9596
|
+
diary: "book"
|
|
9597
|
+
};
|
|
9598
|
+
|
|
8892
9599
|
const subviews = [
|
|
8893
9600
|
{ key: "memory", label: t("memory.explorer.tabMemory") },
|
|
8894
9601
|
{ key: "entity", label: t("memory.explorer.tabEntities") },
|
|
8895
9602
|
{ key: "status", label: t("memory.explorer.tabStatus") },
|
|
8896
9603
|
{ key: "settings", label: t("memory.explorer.tabSettings") },
|
|
8897
9604
|
{ key: "persona", label: t("memory.explorer.tabPersona") },
|
|
9605
|
+
{ key: "affective", label: t("memory.explorer.tabAffective") },
|
|
8898
9606
|
{ key: "diary", label: t("memory.diary.title") }
|
|
8899
9607
|
];
|
|
8900
9608
|
|
|
@@ -8914,7 +9622,7 @@ window.__ModuleLoader__.load({
|
|
|
8914
9622
|
"aria-pressed": String(view === s.key),
|
|
8915
9623
|
onClick: () => leaveMemory(s.key)
|
|
8916
9624
|
},
|
|
8917
|
-
h(Icon, { name: s.key
|
|
9625
|
+
h(Icon, { name: TAB_ICONS[s.key] ?? "settings", size: 14, className: "mneme-vtabico" }),
|
|
8918
9626
|
s.label
|
|
8919
9627
|
))
|
|
8920
9628
|
),
|
|
@@ -9261,6 +9969,7 @@ window.__ModuleLoader__.load({
|
|
|
9261
9969
|
view === "settings" && h("div", { className: "mneme-set" },
|
|
9262
9970
|
h("div", { className: "mneme-set-inner" }, h(SettingsContent, { t }))
|
|
9263
9971
|
),
|
|
9972
|
+
view === "affective" && h(AffectivePanel, { t }),
|
|
9264
9973
|
view === "persona" && h("div", { className: "mneme-set" },
|
|
9265
9974
|
h("div", { className: "mneme-set-inner" }, h(PersonaMainPanel, { t }))
|
|
9266
9975
|
),
|
|
@@ -11096,8 +11805,7 @@ window.__ModuleLoader__.load({
|
|
|
11096
11805
|
const DIARY_BOOKS = [
|
|
11097
11806
|
{ key: "plain", label: "memory.diary.book.plain" },
|
|
11098
11807
|
{ key: "ruled", label: "memory.diary.book.ruled" },
|
|
11099
|
-
{ key: "grid", label: "memory.diary.book.grid" }
|
|
11100
|
-
{ key: "kraft", label: "memory.diary.book.kraft" }
|
|
11808
|
+
{ key: "grid", label: "memory.diary.book.grid" }
|
|
11101
11809
|
];
|
|
11102
11810
|
const DIARY_BOOK_KEY = "dsh-mneme-diary-book";
|
|
11103
11811
|
|
|
@@ -11853,6 +12561,9 @@ window.__ModuleLoader__.load({
|
|
|
11853
12561
|
: draft.byAgent[editing] || emptyPersonaCard();
|
|
11854
12562
|
|
|
11855
12563
|
const save = async (next) => {
|
|
12564
|
+
// 每次保存前先把上一次的「已保存」收回去:否则改完字段它仍挂着,
|
|
12565
|
+
// 先成功后失败时还会与「保存失败」并存——两种都是假成功。
|
|
12566
|
+
setSavedAt(0);
|
|
11856
12567
|
try {
|
|
11857
12568
|
const res = await apiFetch("/api/dsh-mneme/persona", {
|
|
11858
12569
|
method: "PUT",
|
|
@@ -11871,6 +12582,7 @@ window.__ModuleLoader__.load({
|
|
|
11871
12582
|
|
|
11872
12583
|
const setField = (key, value) => {
|
|
11873
12584
|
if (!ready) return;
|
|
12585
|
+
setSavedAt(0);
|
|
11874
12586
|
if (editing === "default") {
|
|
11875
12587
|
setDraft({ ...draft, default: { ...draft.default, [key]: value } });
|
|
11876
12588
|
return;
|
|
@@ -11889,6 +12601,7 @@ window.__ModuleLoader__.load({
|
|
|
11889
12601
|
// 开关即存(与设置页同语义),但仍走三维全量:只换自己这一维。
|
|
11890
12602
|
const toggleEnabled = (checked) => {
|
|
11891
12603
|
if (!ready) return;
|
|
12604
|
+
setSavedAt(0);
|
|
11892
12605
|
const nextCard = { ...current, enabled: checked };
|
|
11893
12606
|
const next = editing === "default"
|
|
11894
12607
|
? { ...draft, default: nextCard }
|
|
@@ -12335,11 +13048,26 @@ window.__ModuleLoader__.load({
|
|
|
12335
13048
|
phase: "loading", repos: [], repo: null, modules: [], edges: [], page: null,
|
|
12336
13049
|
hits: [], q: "", selected: null, disabled: false, error: null,
|
|
12337
13050
|
rebuilding: false, rebuildNote: null, path: "", flags: null, t1: null, toast: "",
|
|
12338
|
-
cliCommand: null, copied: false
|
|
13051
|
+
cliCommand: null, copied: false,
|
|
13052
|
+
// autoRefreshNote:读页时服务端说「这次自动刷新我主动没做」。它不是错误,
|
|
13053
|
+
// 所以不塞进 error、也不进 toast(toast 2.5s 就没了,用户看不到原因)。
|
|
13054
|
+
autoRefreshNote: null,
|
|
13055
|
+
// autoRefreshError:服务端**真的**试过刷新但失败了。与上面那条严格分开:
|
|
13056
|
+
// 把「主动没做」和「做了没成」合成一句,面板就会把一次正确的拒绝说成故障,
|
|
13057
|
+
// 也会把一次真故障说成没事(src/api.js 两个字段分开就是为了这个)。
|
|
13058
|
+
autoRefreshError: null,
|
|
13059
|
+
// 证据复核(Grounded Claims)的两样状态:drift 是 /choro/claims 的
|
|
13060
|
+
// 回体,driftPhase 区分「还没查」「查着」「查出三种结局」。
|
|
13061
|
+
drift: null, driftPhase: "idle"
|
|
12339
13062
|
});
|
|
12340
13063
|
// T1 跑完要重读当前仓库的模块树,但轮询的闭包里拿不到最新的 state.repo:
|
|
12341
13064
|
// 用 ref 记住加载成功的仓库 id,避免把整棵树塞进 effect 依赖里。
|
|
12342
13065
|
const repoIdRef = useRef(null);
|
|
13066
|
+
// 证据复核的请求代次:切仓库 / 重新 load 时 +1。checkDrift 发出前记下当时的
|
|
13067
|
+
// 代次,回来时代次已经变了就直接丢弃结果——否则「在甲仓库上发的 diff,在切到
|
|
13068
|
+
// 乙仓库之后才回来」会把甲仓库的「需要复核」写到乙仓库的页签下,而用户看到的
|
|
13069
|
+
// 是一个根本不属于当前页面的判定(比不显示更糟)。
|
|
13070
|
+
const driftGenRef = useRef(0);
|
|
12343
13071
|
// apiFetch 是裸 fetch:非 2xx 不抛错,返回 Response。这里统一解 JSON 并把
|
|
12344
13072
|
// 状态码带回来——409 disabled(用户没开)/ 503 unavailable(装配缺件)
|
|
12345
13073
|
// 各有各的空态文案(#497 的语义分叉在这里接住)。
|
|
@@ -12386,12 +13114,38 @@ window.__ModuleLoader__.load({
|
|
|
12386
13114
|
// (/choro/modules 的 repo 只有 id/name,没有 stale)。
|
|
12387
13115
|
const loaded = mod.data.repo;
|
|
12388
13116
|
repoIdRef.current = loaded && loaded.id != null ? loaded.id : ref;
|
|
12389
|
-
|
|
13117
|
+
// 切仓库时证据复核的旧结果必须清掉:它是按上一个仓库的 diff 算的,
|
|
13118
|
+
// 留着就会把甲仓库的「需要复核」挂在乙仓库的页签下。
|
|
13119
|
+
// 代次 +1 之后,任何在途的 checkDrift 结果都会被丢弃(见 driftGenRef)。
|
|
13120
|
+
driftGenRef.current += 1;
|
|
13121
|
+
setState((s) => ({ ...s, phase: "ready", repos, repo: loaded, modules: mod.data.modules ?? [], edges: mod.data.edges ?? [], page: null, selected: null, drift: null, driftPhase: "idle", autoRefreshNote: null, autoRefreshError: null }));
|
|
12390
13122
|
} catch (err) {
|
|
12391
13123
|
setState((s) => ({ ...s, phase: "error", error: String(err?.message ?? err) }));
|
|
12392
13124
|
}
|
|
12393
13125
|
}, []);
|
|
12394
13126
|
useEffect(() => { load(); loadFlags(); }, [load, loadFlags]);
|
|
13127
|
+
// 证据复核:手动触发、不自动跑。理由是它要跑一次 git diff(大仓库几十
|
|
13128
|
+
// 毫秒到几秒),每切一页就重算会把浏览拖慢;而「上次生成之后代码变没变」
|
|
13129
|
+
// 这件事用户自己知道什么时候关心。三种结局各自渲染,绝不让「没算出来」
|
|
13130
|
+
// 长得像「一条都没事」(#607 的病根)。
|
|
13131
|
+
const checkDrift = useCallback(async () => {
|
|
13132
|
+
const ref = repoIdRef.current;
|
|
13133
|
+
if (ref == null) return;
|
|
13134
|
+
const gen = driftGenRef.current;
|
|
13135
|
+
const writeIfCurrent = (patch) => setState((s) => (driftGenRef.current === gen ? { ...s, ...patch } : s));
|
|
13136
|
+
writeIfCurrent({ driftPhase: "loading" });
|
|
13137
|
+
// 只写「还是同一次请求、还是同一个仓库」的结果。
|
|
13138
|
+
try {
|
|
13139
|
+
const { status, data } = await readJson(await apiFetch(`/api/dsh-mneme/choro/claims?repo=${encodeURIComponent(ref)}`));
|
|
13140
|
+
if (status !== 200 || data.error) {
|
|
13141
|
+
writeIfCurrent({ driftPhase: "failed", drift: { error: data.error ?? `claims ${status}` } });
|
|
13142
|
+
return;
|
|
13143
|
+
}
|
|
13144
|
+
writeIfCurrent({ driftPhase: "done", drift: data });
|
|
13145
|
+
} catch (err) {
|
|
13146
|
+
writeIfCurrent({ driftPhase: "failed", drift: { error: String(err?.message ?? err) } });
|
|
13147
|
+
}
|
|
13148
|
+
}, []);
|
|
12395
13149
|
const openPage = useCallback(async (slug) => {
|
|
12396
13150
|
const repo = state.repo;
|
|
12397
13151
|
if (!repo) return;
|
|
@@ -12399,7 +13153,21 @@ window.__ModuleLoader__.load({
|
|
|
12399
13153
|
try {
|
|
12400
13154
|
const { status, data } = await readJson(await apiFetch(`/api/dsh-mneme/choro/page?repo=${encodeURIComponent(repo.id)}&slug=${encodeURIComponent(slug)}`));
|
|
12401
13155
|
if (status !== 200 || data.error) { setState((s) => ({ ...s, page: { error: data.error ?? `page ${status}` } })); return; }
|
|
12402
|
-
|
|
13156
|
+
// 自动刷新被主动跳过:这条要说出来。不说的话,用户看到的是「代码已变」
|
|
13157
|
+
// 徽章一直亮着、页面还是旧的,只会以为刷新坏了(#607 那类假成功的反面)。
|
|
13158
|
+
// 文案自己拼而不直接用服务端那句:那句话是中文的,进不了英文界面;
|
|
13159
|
+
// 页数用服务端算出的 enrichedPages,只有它缺的时候才退回服务端原话。
|
|
13160
|
+
const skipped = data.autoRefreshSkipped === "t1-loss"
|
|
13161
|
+
? (Number(data.repo?.enrichedPages ?? 0) > 0
|
|
13162
|
+
? t("wiki.autoRefresh.skipped").replace("{n}", String(data.repo.enrichedPages))
|
|
13163
|
+
: (data.autoRefreshSkipNote || t("wiki.autoRefresh.skippedGeneric")))
|
|
13164
|
+
: null;
|
|
13165
|
+
// 真失败要单独带上:刷新没成时页面还是旧的、徽章还亮着,不说出来
|
|
13166
|
+
// 就是「点了、看起来正常、其实是上次的内容」。
|
|
13167
|
+
const refreshError = typeof data.autoRefreshError === "string" && data.autoRefreshError
|
|
13168
|
+
? data.autoRefreshError
|
|
13169
|
+
: null;
|
|
13170
|
+
setState((s) => ({ ...s, page: data.page, autoRefreshNote: skipped, autoRefreshError: refreshError }));
|
|
12403
13171
|
} catch (err) {
|
|
12404
13172
|
setState((s) => ({ ...s, page: { error: String(err?.message ?? err) } }));
|
|
12405
13173
|
}
|
|
@@ -12435,6 +13203,9 @@ window.__ModuleLoader__.load({
|
|
|
12435
13203
|
setState((s) => ({
|
|
12436
13204
|
...s,
|
|
12437
13205
|
rebuilding: false,
|
|
13206
|
+
// 重建已经真的发生了,那条「自动刷新没做」的提示就该退场。
|
|
13207
|
+
autoRefreshNote: null,
|
|
13208
|
+
autoRefreshError: null,
|
|
12438
13209
|
rebuildNote: `${t("wiki.rebuild.done")}: ${r.moduleCount ?? "?"} ${t("wiki.rebuild.modules")} / ${r.pageCount ?? "?"} ${t("wiki.rebuild.pages")}${loss ? ` — ${loss}` : ""}`
|
|
12439
13210
|
}));
|
|
12440
13211
|
await load(repo.id);
|
|
@@ -12639,9 +13410,48 @@ window.__ModuleLoader__.load({
|
|
|
12639
13410
|
onClick: () => load(r.id)
|
|
12640
13411
|
}, `${r.name}`)));
|
|
12641
13412
|
}
|
|
13413
|
+
// 证据复核的三种结局在这里一次性判完,渲染层不再现算:
|
|
13414
|
+
// 「基线就是 HEAD」和「算不出来」必须走两条不同的分支,屏幕上也要是两句
|
|
13415
|
+
// 不同的话——把两者显示成同一个样子,等于把「没算」当成「没事」。
|
|
13416
|
+
const drift = state.drift;
|
|
13417
|
+
const driftBySlug = new Map();
|
|
13418
|
+
if (drift && Array.isArray(drift.pages)) {
|
|
13419
|
+
for (const dp of drift.pages) driftBySlug.set(dp.slug, dp);
|
|
13420
|
+
}
|
|
13421
|
+
const driftDone = state.driftPhase === "done" && drift && !drift.error;
|
|
13422
|
+
const driftClean = driftDone && drift.clean === true;
|
|
13423
|
+
const driftAnchorStale = driftDone && drift.anchorStale === true;
|
|
13424
|
+
// 「git 读不到」与「基线提交没了」是两种完全不同的处境:前者是仓库不是 git 目录
|
|
13425
|
+
// 或不可读(去修环境),后者是 force-push/浅克隆把起点截断了(去重建骨架)。
|
|
13426
|
+
// 两句 i18n 早就写好了,只是原来没接上,一律说成后者。
|
|
13427
|
+
const driftGitUnreadable = driftAnchorStale && drift.reason === "git-unreadable";
|
|
13428
|
+
const driftTotals = (driftDone && drift.totals) || null;
|
|
13429
|
+
const driftRisk = driftTotals ? driftTotals.atRiskClaims : 0;
|
|
13430
|
+
// 「比不了」的页数(验证基线认不出 + 从来没记过 + 有语义段却没 claims)单独计数:
|
|
13431
|
+
// 它们一条都不判,不能混进「0 条需要复核」里当成没事。inconsistentPages 原来
|
|
13432
|
+
// 完全没进这个汇总,于是「所有页都是残局」也会显示成「这次改动没碰到任何结论」。
|
|
13433
|
+
const driftUndecidable = driftTotals
|
|
13434
|
+
? (driftTotals.baselineUnknownPages || 0)
|
|
13435
|
+
+ (driftTotals.anchorStalePages || 0)
|
|
13436
|
+
+ (driftTotals.inconsistentPages || 0)
|
|
13437
|
+
: 0;
|
|
13438
|
+
// 提交号显示前 7 位就够认,长 sha 会把这一行挤爆(完整值仍在 title 里)。
|
|
13439
|
+
const shortSha = (sha) => String(sha ?? "").slice(0, 7);
|
|
13440
|
+
// 四种命中原因各自一句:混成「失效」会让只是行号挪了的结论也被喊去重写,
|
|
13441
|
+
// 喊几次就没人信这个标记了。
|
|
13442
|
+
const driftReasonText = (c) => {
|
|
13443
|
+
if (c.reason === "touched") return t("wiki.drift.touched");
|
|
13444
|
+
if (c.reason === "shifted") return t("wiki.drift.shifted").replace("{n}", String(c.shift ?? 0));
|
|
13445
|
+
if (c.reason === "deleted") return t("wiki.drift.deleted");
|
|
13446
|
+
if (c.reason === "unverifiable") return t("wiki.drift.unverifiable");
|
|
13447
|
+
return String(c.reason ?? "");
|
|
13448
|
+
};
|
|
12642
13449
|
const tree = [];
|
|
12643
13450
|
for (const mod of state.modules) {
|
|
12644
13451
|
const depth = (mod.slug.match(/-/g) || []).length;
|
|
13452
|
+
// 树里直接挂「这页有几条要复核」:用户不用先点开才知道哪页被改动碰到。
|
|
13453
|
+
const dp = driftBySlug.get(mod.slug);
|
|
13454
|
+
const riskN = dp && dp.atRisk ? dp.atRisk.length : 0;
|
|
12645
13455
|
tree.push(h("button", {
|
|
12646
13456
|
key: mod.slug,
|
|
12647
13457
|
type: "button",
|
|
@@ -12649,13 +13459,49 @@ window.__ModuleLoader__.load({
|
|
|
12649
13459
|
style: { paddingLeft: `${8 + Math.min(depth, 4) * 14}px` },
|
|
12650
13460
|
title: mod.title,
|
|
12651
13461
|
onClick: () => openPage(mod.slug)
|
|
12652
|
-
}, mod.slug
|
|
13462
|
+
}, mod.slug,
|
|
13463
|
+
riskN > 0 && h("span", { className: "mneme-badge mneme-badge--pending mneme-wikidriftn" }, String(riskN)),
|
|
13464
|
+
!riskN && dp && dp.inconsistent && h("span", { className: "mneme-wikidriftn mneme-wikidriftn--flat" }, "!"),
|
|
13465
|
+
// 这页比不了时挂个问号:什么都不挂会让它和「比过、干净」长得一样。
|
|
13466
|
+
!riskN && dp && !dp.inconsistent && (dp.baselineUnknown || dp.anchorStale) &&
|
|
13467
|
+
h("span", {
|
|
13468
|
+
className: "mneme-wikidriftn mneme-wikidriftn--unknown",
|
|
13469
|
+
title: dp.baselineUnknown ? t("wiki.drift.verifiedUnknown") : t("wiki.drift.anchorStalePage")
|
|
13470
|
+
}, "?")));
|
|
12653
13471
|
}
|
|
12654
13472
|
const pageBody = state.page
|
|
12655
13473
|
? (state.page.error
|
|
12656
13474
|
? h("div", { className: "mneme-wikiempty" }, state.page.error === "page-missing" ? t("wiki.state.pageMissing") : state.page.error)
|
|
12657
13475
|
: renderWikiMarkdown(state.page.content))
|
|
12658
13476
|
: h("div", { className: "mneme-wikiempty" }, t("wiki.state.pickPage"));
|
|
13477
|
+
// 当前这页的证据复核结果:放在正文上方而不是塞进正文,因为它是「关于
|
|
13478
|
+
// 正文的事实」,不是正文本身——混进 MD 渲染里会被下次重建覆盖掉。
|
|
13479
|
+
const selectedDrift = state.selected ? driftBySlug.get(state.selected) : null;
|
|
13480
|
+
// 「这页对着哪个提交验证的」是常驻信息(不论有没有风险都该看到),所以它是
|
|
13481
|
+
// 正文上方单独一行,而不是塞进下面那张「有风险才出现」的卡里。
|
|
13482
|
+
const pageProvenance = driftDone && selectedDrift
|
|
13483
|
+
? (selectedDrift.verifiedBaseline
|
|
13484
|
+
? h("div", { className: "mneme-wikidriftverified", title: selectedDrift.verifiedBaseline },
|
|
13485
|
+
t("wiki.drift.verified").replace("{sha}", shortSha(selectedDrift.verifiedBaseline)))
|
|
13486
|
+
// 老数据:没有逐页验证点,退到全仓基线比对。必须说清这是**保底起点**,
|
|
13487
|
+
// 不能长成「对着 xxxx 验证」——那是把近似值冒充成真值。
|
|
13488
|
+
: (selectedDrift.baselineApprox && selectedDrift.diffBaseline
|
|
13489
|
+
? h("div", { className: "mneme-wikidriftverified", title: selectedDrift.diffBaseline },
|
|
13490
|
+
t("wiki.drift.verifiedApprox").replace("{sha}", shortSha(selectedDrift.diffBaseline)))
|
|
13491
|
+
: (selectedDrift.claims > 0
|
|
13492
|
+
? h("div", { className: "mneme-wikidriftverified" }, t("wiki.drift.verifiedUnknown"))
|
|
13493
|
+
: null)))
|
|
13494
|
+
: null;
|
|
13495
|
+
const pageDrift = selectedDrift && selectedDrift.atRisk && selectedDrift.atRisk.length > 0
|
|
13496
|
+
? h("div", { className: "mneme-wikidriftpage", role: "note" },
|
|
13497
|
+
h("div", { className: "mneme-wikidriftpagehead" },
|
|
13498
|
+
`${t("wiki.drift")} · ${selectedDrift.atRisk.length}`),
|
|
13499
|
+
selectedDrift.atRisk.map((c) => h("div", { key: String(c.claimId ?? c.statement), className: "mneme-wikidriftrow" },
|
|
13500
|
+
h("span", { className: "mneme-wikidriftwhy" }, driftReasonText(c)),
|
|
13501
|
+
h("span", { className: "mneme-wikidriftpath" },
|
|
13502
|
+
`${c.sourcePath}${c.startLine ? `:${c.startLine}${c.endLine && c.endLine !== c.startLine ? `-${c.endLine}` : ""}` : ""}`),
|
|
13503
|
+
h("div", { className: "mneme-wikidriftstmt" }, c.statement))))
|
|
13504
|
+
: null;
|
|
12659
13505
|
const flags = state.flags || {};
|
|
12660
13506
|
const t1Running = !!(state.t1 && state.t1.state === "running");
|
|
12661
13507
|
return h("div", { className: "mneme-panel" },
|
|
@@ -12709,7 +13555,41 @@ window.__ModuleLoader__.load({
|
|
|
12709
13555
|
title: t("memory.features.chorographyT1FromPanel.hint"),
|
|
12710
13556
|
onClick: startT1
|
|
12711
13557
|
}, t1Running ? t("wiki.t1.running") : t("wiki.t1")),
|
|
13558
|
+
// 证据复核是只读动作(一次 git diff + 库内比对,不调模型),所以它
|
|
13559
|
+
// 和「重建骨架」并列、不用二次确认;但绝不自动跑,见 checkDrift。
|
|
13560
|
+
h("button", {
|
|
13561
|
+
type: "button",
|
|
13562
|
+
className: "mneme-wikibtn",
|
|
13563
|
+
disabled: state.driftPhase === "loading",
|
|
13564
|
+
title: t("wiki.drift.hint"),
|
|
13565
|
+
onClick: checkDrift
|
|
13566
|
+
}, state.driftPhase === "loading" ? t("wiki.drift.loading") : t("wiki.drift")),
|
|
12712
13567
|
state.rebuildNote && h("span", { className: "mneme-wikirebuildnote" }, state.rebuildNote)),
|
|
13568
|
+
// 「自动刷新我主动没做」的提示条。放在顶栏下面而不是 toast 里:它描述的
|
|
13569
|
+
// 是一个仍然成立的状态(库里还有富化页),不是一次性的动作回执。
|
|
13570
|
+
state.autoRefreshNote && h("div", { className: "mneme-wikidriftbar mneme-wikidriftbar--warn", role: "status" },
|
|
13571
|
+
state.autoRefreshNote),
|
|
13572
|
+
// 「试过刷新但失败了」单独一条:它不是无事发生,也不是「主动没做」。
|
|
13573
|
+
// 旧实现在这里整段丢字段,用户的感受就是「点了没反应」。
|
|
13574
|
+
state.autoRefreshError && h("div", { className: "mneme-wikidriftbar mneme-wikidriftbar--warn", role: "status" },
|
|
13575
|
+
`${t("wiki.autoRefresh.failed")}: ${state.autoRefreshError}`),
|
|
13576
|
+
// 证据复核回执条:三种结局三种说法,「比对失败」单独一条 --
|
|
13577
|
+
// 它既不是「没事」也不是「有风险」,混进任何一边都是撒谎。
|
|
13578
|
+
state.driftPhase === "failed" && h("div", { className: "mneme-wikidriftbar mneme-wikidriftbar--warn", role: "status" },
|
|
13579
|
+
`${t("wiki.drift.blocked")}: ${(state.drift && state.drift.error) || ""}`),
|
|
13580
|
+
driftDone && driftAnchorStale && h("div", { className: "mneme-wikidriftbar mneme-wikidriftbar--warn", role: "status" },
|
|
13581
|
+
driftGitUnreadable ? t("wiki.drift.gitUnreadable") : t("wiki.drift.anchorStale")),
|
|
13582
|
+
driftDone && !driftAnchorStale && driftClean && h("div", { className: "mneme-wikidriftbar", role: "status" },
|
|
13583
|
+
t("wiki.drift.clean")),
|
|
13584
|
+
driftDone && !driftAnchorStale && !driftClean && h("div", { className: "mneme-wikidriftbar", role: "status" },
|
|
13585
|
+
t("wiki.drift.summary")
|
|
13586
|
+
.replace("{pages}", String(driftTotals.pages))
|
|
13587
|
+
.replace("{claims}", String(driftTotals.claims))
|
|
13588
|
+
.replace("{risk}", String(driftRisk)),
|
|
13589
|
+
driftRisk === 0 && driftUndecidable === 0 && h("span", { className: "mneme-wikidriftnone" }, ` · ${t("wiki.drift.none")}`),
|
|
13590
|
+
// 有页比不了时,回执条必须自己说出来:否则「0 条需要复核」会被读成没事。
|
|
13591
|
+
driftUndecidable > 0 && h("span", { className: "mneme-wikidriftnone" },
|
|
13592
|
+
` · ${t("wiki.drift.undecidable").replace("{n}", String(driftUndecidable))}`)),
|
|
12713
13593
|
h("div", { className: "mneme-wikibody" },
|
|
12714
13594
|
h("nav", { className: "mneme-wikitree", "aria-label": t("wiki.tree.title") }, tree),
|
|
12715
13595
|
h("div", { className: "mneme-wikipage" },
|
|
@@ -12721,6 +13601,12 @@ window.__ModuleLoader__.load({
|
|
|
12721
13601
|
onClick: () => { setState((s) => ({ ...s, hits: [] })); openPage(hit.slug); }
|
|
12722
13602
|
}, `${hit.slug} · ${hit.section} · L${hit.line}: ${hit.excerpt}`)),
|
|
12723
13603
|
h("button", { type: "button", className: "mneme-wikihitsclose", onClick: () => setState((s) => ({ ...s, hits: [] })) }, "×")),
|
|
13604
|
+
pageProvenance,
|
|
13605
|
+
pageDrift,
|
|
13606
|
+
// 正文有语义段、库里零条结论:这页的证据根本无从核对,必须自己说
|
|
13607
|
+
// 出来,否则它会永远显示成「干净」——那是最坏的一种假成功。
|
|
13608
|
+
selectedDrift && selectedDrift.inconsistent && h("div", { className: "mneme-wikidriftbar mneme-wikidriftbar--warn", role: "note" },
|
|
13609
|
+
t("wiki.drift.inconsistent")),
|
|
12724
13610
|
pageBody)),
|
|
12725
13611
|
// T1 进度:只在一个 job 活着时出现。数字来自 /choro/t1/status 的
|
|
12726
13612
|
// modules 汇总,不再由面板自己数模块——那是任务侧的真相。
|