dsh-lite-memory 0.17.0 → 0.18.19
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 +15 -15
- package/TRIGGERS.md +1 -1
- package/client.js +343 -44
- package/docs/BENCH.md +10 -8
- package/docs/RELEASE-0.18.0.md +35 -0
- package/docs/RELEASE-0.18.1.md +18 -0
- package/docs/RELEASE-0.18.10.md +25 -0
- package/docs/RELEASE-0.18.11.md +48 -0
- package/docs/RELEASE-0.18.12.md +27 -0
- package/docs/RELEASE-0.18.13.md +13 -0
- package/docs/RELEASE-0.18.14.md +13 -0
- package/docs/RELEASE-0.18.15.md +5 -0
- package/docs/RELEASE-0.18.2.md +21 -0
- package/docs/RELEASE-0.18.3.md +16 -0
- package/docs/RELEASE-0.18.4.md +15 -0
- package/docs/RELEASE-0.18.5.md +21 -0
- package/docs/RELEASE-0.18.6.md +18 -0
- package/docs/RELEASE-0.18.7.md +23 -0
- package/docs/RELEASE-0.18.8.md +18 -0
- package/docs/RELEASE-0.18.9.md +24 -0
- package/index.js +403 -131
- package/learning.js +65 -6
- package/package.json +7 -4
- package/state.js +56 -8
- package/store.js +361 -33
- package/test/client.test.mjs +79 -17
- package/test/contract.test.mjs +231 -93
- package/test/control.test.mjs +2 -2
- package/test/crossing.test.mjs +22 -1
- package/test/eval-seed.test.mjs +113 -0
- package/test/evolution-read.test.mjs +33 -10
- package/test/ha.test.mjs +186 -0
- package/test/layer-read.test.mjs +293 -0
- package/test/learning.test.mjs +80 -0
- package/test/palace.test.mjs +2 -0
- package/test/recall.test.mjs +80 -9
- package/test/record.test.mjs +130 -0
- package/test/safety.test.mjs +119 -0
- package/test/state.test.mjs +57 -1
- package/test/store.test.mjs +49 -5
- package/test/view.test.mjs +3 -0
- package/view.js +97 -13
package/client.js
CHANGED
|
@@ -82,22 +82,26 @@ window.__ModuleLoader__.load({
|
|
|
82
82
|
return meta;
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
-
const isMemoryId = (value) => typeof value === 'string' && /^mem_[a-z0-9]{6,}$/.test(value);
|
|
86
85
|
const isCandidateId = (value) => typeof value === 'string' && /^sug_[a-z0-9]{6,}$/.test(value);
|
|
87
86
|
|
|
88
|
-
/**
|
|
87
|
+
/** 一条引用的规范形态:只有标题、范围与版次;id 不进界面。 */
|
|
89
88
|
const citationOf = (meta, kind) => {
|
|
90
|
-
if (!meta || !
|
|
89
|
+
if (!meta || typeof meta.title !== 'string' || !meta.title.trim()) return null;
|
|
91
90
|
if (kind === 'memory-save' && meta.changed !== true) return null;
|
|
92
91
|
return {
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
title: meta.title,
|
|
93
|
+
key: `${meta.scope === 'global' ? 'global' : 'project'}\u0000${meta.title}`, // ui-copy-ignore
|
|
95
94
|
scope: meta.scope === 'global' ? 'global' : 'project',
|
|
96
95
|
revision: Number.isInteger(meta.revision) ? meta.revision : 1,
|
|
97
96
|
status: typeof meta.status === 'string' ? meta.status : 'ok',
|
|
98
97
|
origin: meta.origin === 'project_evolution' ? 'project_evolution' : 'user_explicit',
|
|
99
98
|
kind,
|
|
100
99
|
exposure: kind === 'memory-save' || kind === 'memory-forget' ? 'update' : 'reference',
|
|
100
|
+
/** 条目类型:读正文看 recordKind,检索看 ref 自己的 kind;工具名不是类型。 */
|
|
101
|
+
entryKind: typeof meta.kind === 'string' && Object.prototype.hasOwnProperty.call(KIND_LABEL, meta.kind)
|
|
102
|
+
? meta.kind
|
|
103
|
+
: (typeof meta.recordKind === 'string' ? meta.recordKind : ''),
|
|
104
|
+
updated: typeof meta.updated === 'string' ? meta.updated : '',
|
|
101
105
|
reason: typeof meta.reason === 'string' ? meta.reason : '',
|
|
102
106
|
reasonCode: typeof meta.reasonCode === 'string' ? meta.reasonCode : '',
|
|
103
107
|
noTriggerReason: typeof meta.noTriggerReason === 'string' ? meta.noTriggerReason : '',
|
|
@@ -153,7 +157,7 @@ window.__ModuleLoader__.load({
|
|
|
153
157
|
const seen = new Map();
|
|
154
158
|
const out = [];
|
|
155
159
|
for (const item of items) {
|
|
156
|
-
const key = `${item.scope}:${item.
|
|
160
|
+
const key = `${item.scope}:${item.key}:${item.exposure}`;
|
|
157
161
|
const at = seen.get(key);
|
|
158
162
|
if (at === undefined) {
|
|
159
163
|
seen.set(key, out.length);
|
|
@@ -253,9 +257,51 @@ window.__ModuleLoader__.load({
|
|
|
253
257
|
};
|
|
254
258
|
};
|
|
255
259
|
|
|
260
|
+
/** 一行展示的身份:曝光类型 + 文案 + 正文;拦截结果没有 id,只能按内容认。 */
|
|
261
|
+
const rowKeyOf = (toolName, block) => {
|
|
262
|
+
const text = settledText(block);
|
|
263
|
+
return text ? `${toolName}|${text}` : '';
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
/** 同一组行每次出现的去留:同键只留第一次。 */
|
|
267
|
+
const rowKeepFlags = (keys) => {
|
|
268
|
+
const seen = new Set();
|
|
269
|
+
return keys.map((key) => {
|
|
270
|
+
if (!key) return true;
|
|
271
|
+
if (seen.has(key)) return false;
|
|
272
|
+
seen.add(key);
|
|
273
|
+
return true;
|
|
274
|
+
});
|
|
275
|
+
};
|
|
276
|
+
|
|
256
277
|
const Row = (props) => {
|
|
257
278
|
const line = props.phase === 'preparing' ? null : firstLine(settledText(props.block));
|
|
258
|
-
|
|
279
|
+
const key = props.phase === 'preparing' ? '' : rowKeyOf(props.toolName, props.block);
|
|
280
|
+
const nodeRef = React.useRef(null);
|
|
281
|
+
const [dup, setDup] = React.useState(false);
|
|
282
|
+
const useLayout = React.useLayoutEffect || React.useEffect;
|
|
283
|
+
useLayout(() => {
|
|
284
|
+
const node = nodeRef.current;
|
|
285
|
+
if (!key || !node || typeof node.closest !== 'function') return;
|
|
286
|
+
const turnBox = node.closest('[data-chat-turn]');
|
|
287
|
+
const turn = turnBox ? turnBox.getAttribute('data-chat-turn') : null;
|
|
288
|
+
const rootScope = node.ownerDocument || node.parentElement;
|
|
289
|
+
if (!rootScope || typeof rootScope.querySelectorAll !== 'function') return;
|
|
290
|
+
const peers = Array.prototype.filter.call(rootScope.querySelectorAll('[data-memory-row]'), (peer) => {
|
|
291
|
+
if (!turn) return peer.parentElement === node.parentElement;
|
|
292
|
+
const box = peer.closest('[data-chat-turn]');
|
|
293
|
+
return box !== null && box.getAttribute('data-chat-turn') === turn;
|
|
294
|
+
});
|
|
295
|
+
const at = peers.indexOf(node);
|
|
296
|
+
if (at < 0) return;
|
|
297
|
+
setDup(!rowKeepFlags(peers.map((peer) => peer.getAttribute('data-memory-row') || ''))[at]);
|
|
298
|
+
}, [key]);
|
|
299
|
+
return h('div', {
|
|
300
|
+
ref: nodeRef,
|
|
301
|
+
'data-tool': props.toolName,
|
|
302
|
+
'data-memory-row': key || undefined,
|
|
303
|
+
style: dup ? { ...ROW, display: 'none' } : ROW,
|
|
304
|
+
}, h('span', { style: DOT }, '·'), h('span', { style: LABEL }, line ?? RUNNING[props.toolName] ?? 'memory'));
|
|
259
305
|
};
|
|
260
306
|
|
|
261
307
|
/** 建议行:候选 id 来自持久元数据,不读文案。 */
|
|
@@ -293,7 +339,7 @@ window.__ModuleLoader__.load({
|
|
|
293
339
|
const undoRefOf = (meta, cwd) => (meta
|
|
294
340
|
&& meta.kind === 'memory-save'
|
|
295
341
|
&& meta.changed === true
|
|
296
|
-
&&
|
|
342
|
+
&& typeof meta.title === 'string' && meta.title.trim().length > 0
|
|
297
343
|
&& Number.isInteger(meta.revision)
|
|
298
344
|
&& typeof cwd === 'string'
|
|
299
345
|
&& cwd.length > 0
|
|
@@ -312,7 +358,7 @@ window.__ModuleLoader__.load({
|
|
|
312
358
|
const drop = () => {
|
|
313
359
|
if (!canDrop) return;
|
|
314
360
|
setState('saving');
|
|
315
|
-
post('/lite-memory/undo', { op: 'undo',
|
|
361
|
+
post('/lite-memory/undo', { op: 'undo', title: changed.title, revision: changed.revision, cwd }).then((result) => {
|
|
316
362
|
if (succeeded(result)) {
|
|
317
363
|
setState('dropped');
|
|
318
364
|
setNote(result.body && result.body.mode === 'restored' ? 'memory · restored' : 'memory · archived');
|
|
@@ -464,7 +510,7 @@ window.__ModuleLoader__.load({
|
|
|
464
510
|
if (ref.messageId !== undefined) return `消息 ${ref.messageId}`;
|
|
465
511
|
if (ref.turn !== undefined) return `第 ${ref.turn} 轮`;
|
|
466
512
|
if (ref.turnIndex !== undefined) return `第 ${ref.turnIndex} 轮`;
|
|
467
|
-
return
|
|
513
|
+
return `引用 ${index + 1}`;
|
|
468
514
|
};
|
|
469
515
|
const refHrefOf = (ref) => (ref && typeof ref === 'object'
|
|
470
516
|
? [ref.href, ref.url, ref.link].find((value) => typeof value === 'string' && value)
|
|
@@ -540,13 +586,27 @@ window.__ModuleLoader__.load({
|
|
|
540
586
|
const REF_META = { display: 'flex', alignItems: 'center', gap: '6px', margin: '0 0 2px 2px', fontSize: '11px', lineHeight: '18px', color: 'var(--dsw-alias-label-secondary)' };
|
|
541
587
|
const REF_BODY = { margin: '2px 0 3px', padding: '3px 0 3px 9px', display: 'flex', flexDirection: 'column', gap: '3px', fontSize: '12px', lineHeight: '1.65', maxHeight: '180px', overflowY: 'auto' };
|
|
542
588
|
|
|
543
|
-
/**
|
|
589
|
+
/** 出处一句话:类型、项目/全局、来源、状态。 */
|
|
590
|
+
/** 只管最近一个月:更早或没时间就不占位。 */
|
|
591
|
+
const agoLabel = (value) => {
|
|
592
|
+
const at = Date.parse(value);
|
|
593
|
+
if (!Number.isFinite(at)) return '';
|
|
594
|
+
const days = Math.floor((Date.now() - at) / 86_400_000);
|
|
595
|
+
if (days <= 0) return '今天更新';
|
|
596
|
+
if (days === 1) return '昨天更新';
|
|
597
|
+
if (days < 30) return `${days} 天前更新`;
|
|
598
|
+
return '';
|
|
599
|
+
};
|
|
544
600
|
const refMetaOf = (item) => {
|
|
545
|
-
|
|
601
|
+
/** 会话里只标层与来源:类型留给面板和工具,不占引用行的宽度。 */
|
|
602
|
+
const layer = `${item.scope === 'global' ? '[全局]' : '[项目]'}${item.origin === 'project_evolution' ? '[自进化]' : ''}`;
|
|
603
|
+
const parts = [];
|
|
604
|
+
const ago = agoLabel(item.updated);
|
|
605
|
+
if (ago) parts.push(ago);
|
|
546
606
|
if (item.status === 'archived') parts.push('已归档');
|
|
547
607
|
const processing = PROCESSING_LABEL[item.processingStatus] || '';
|
|
548
608
|
if (processing) parts.push(processing);
|
|
549
|
-
return parts.join(' · ');
|
|
609
|
+
return [layer, ...parts].join(' · ');
|
|
550
610
|
};
|
|
551
611
|
|
|
552
612
|
const RefChevron = (props) => h('svg', {
|
|
@@ -564,8 +624,8 @@ window.__ModuleLoader__.load({
|
|
|
564
624
|
onMouseEnter: () => setHover(true),
|
|
565
625
|
onMouseLeave: () => setHover(false),
|
|
566
626
|
onClick: props.onShow, // ui-copy-ignore
|
|
567
|
-
'aria-label': `查看记忆 ${item.title ||
|
|
568
|
-
title: reason ? `${item.title ||
|
|
627
|
+
'aria-label': `查看记忆 ${item.title || '记忆'}${reason ? `,${reason}` : ''}`,
|
|
628
|
+
title: reason ? `${item.title || '记忆'},${reason}` : (item.title || '记忆'),
|
|
569
629
|
style: { // ui-copy-ignore
|
|
570
630
|
display: 'flex', alignItems: 'flex-start', gap: '7px', width: '100%', minWidth: 0, textAlign: 'left',
|
|
571
631
|
border: 'none', borderRadius: '8px', padding: '3px 7px', cursor: 'pointer', color: 'inherit',
|
|
@@ -575,7 +635,7 @@ window.__ModuleLoader__.load({
|
|
|
575
635
|
},
|
|
576
636
|
h('span', { 'aria-hidden': 'true', style: { flex: '0 0 auto', alignSelf: 'stretch', width: '2px', minHeight: '10px', borderRadius: '999px', background: props.tone, opacity: 0.85 } }),
|
|
577
637
|
h('span', { style: { display: 'flex', flexDirection: 'column', gap: '1px', flex: '1 1 auto', minWidth: 0 } },
|
|
578
|
-
h('span', { style: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, item.title ||
|
|
638
|
+
h('span', { style: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, item.title || '记忆'),
|
|
579
639
|
h('span', { style: { display: 'flex', alignItems: 'center', gap: '6px', minWidth: 0, fontSize: '11px', lineHeight: '16px', color: 'var(--dsw-alias-label-secondary)' } },
|
|
580
640
|
reason ? h('span', { style: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, reason) : null,
|
|
581
641
|
h('span', { style: { marginLeft: 'auto', flex: '0 0 auto', fontVariantNumeric: 'tabular-nums' } }, refMetaOf(item)),
|
|
@@ -614,28 +674,28 @@ window.__ModuleLoader__.load({
|
|
|
614
674
|
setOpen(false);
|
|
615
675
|
};
|
|
616
676
|
const show = (item) => {
|
|
617
|
-
if (shown && shown.
|
|
677
|
+
if (shown && shown.key === item.key) {
|
|
618
678
|
close();
|
|
619
679
|
return;
|
|
620
680
|
}
|
|
621
681
|
const request = generation.current + 1;
|
|
622
682
|
generation.current = request;
|
|
623
683
|
setOpen(true);
|
|
624
|
-
setShown({
|
|
684
|
+
setShown({ key: item.key, ref: item, loading: true });
|
|
625
685
|
setNote('');
|
|
626
|
-
post('/lite-memory/body', {
|
|
686
|
+
post('/lite-memory/body', { title: item.title, scope: item.scope, revision: item.revision, sessionId }).then((result) => {
|
|
627
687
|
if (!alive.current || generation.current !== request) return;
|
|
628
688
|
const payload = result.ok && result.body ? result.body : null;
|
|
629
689
|
if (!payload || payload.ok !== true) {
|
|
630
|
-
setShown({
|
|
690
|
+
setShown({ key: item.key, ref: item, invalid: true });
|
|
631
691
|
return;
|
|
632
692
|
}
|
|
633
693
|
if (payload.status === 'gone') {
|
|
634
|
-
setShown({
|
|
694
|
+
setShown({ key: item.key, ref: item, gone: true });
|
|
635
695
|
return;
|
|
636
696
|
}
|
|
637
697
|
setShown({
|
|
638
|
-
|
|
698
|
+
key: item.key,
|
|
639
699
|
ref: item,
|
|
640
700
|
title: typeof payload.title === 'string' ? payload.title : '',
|
|
641
701
|
when: typeof payload.when === 'string' ? payload.when : '',
|
|
@@ -651,11 +711,11 @@ window.__ModuleLoader__.load({
|
|
|
651
711
|
});
|
|
652
712
|
};
|
|
653
713
|
const rows = open ? items.map((item) => h(RefRow, {
|
|
654
|
-
key: `${item.
|
|
655
|
-
item, tone, active: Boolean(shown && shown.
|
|
714
|
+
key: `${item.key}|${item.revision}`, // ui-copy-ignore
|
|
715
|
+
item, tone, active: Boolean(shown && shown.key === item.key),
|
|
656
716
|
onShow: () => show(item),
|
|
657
717
|
})) : [];
|
|
658
|
-
const detail = shown && shown.ref && items.some((item) => item.
|
|
718
|
+
const detail = shown && shown.ref && items.some((item) => item.key === shown.key && item.revision === shown.ref.revision)
|
|
659
719
|
? h('div', { style: { ...REF_BODY, borderLeft: `2px solid ${tone}` } },
|
|
660
720
|
shown.loading ? h('span', { style: REF_META }, '读取中…') : null,
|
|
661
721
|
shown.gone ? h('span', { style: REF_META }, '这条记忆已删除') : null,
|
|
@@ -770,6 +830,64 @@ window.__ModuleLoader__.load({
|
|
|
770
830
|
return false;
|
|
771
831
|
}
|
|
772
832
|
};
|
|
833
|
+
/**
|
|
834
|
+
* 解释模式只在本机、按会话记;默认关,零命中原因不打扰。
|
|
835
|
+
* 面板开关与对话末尾共用同一条广播。
|
|
836
|
+
*/
|
|
837
|
+
const explainBus = (() => {
|
|
838
|
+
const key = '__liteMemoryExplainBus'; // ui-copy-ignore
|
|
839
|
+
if (!(globalThis[key] instanceof Set)) globalThis[key] = new Set();
|
|
840
|
+
return globalThis[key];
|
|
841
|
+
})();
|
|
842
|
+
const explainKeyOf = (sessionId) => `lite-memory:explain:${sessionId || 'default'}`;
|
|
843
|
+
const explainModeOf = (sessionId) => {
|
|
844
|
+
const store = displayStoreOf();
|
|
845
|
+
if (!store) return false;
|
|
846
|
+
try {
|
|
847
|
+
return store.getItem(explainKeyOf(sessionId)) === '1';
|
|
848
|
+
} catch {
|
|
849
|
+
return false;
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
const setExplainMode = (sessionId, on) => {
|
|
853
|
+
const store = displayStoreOf();
|
|
854
|
+
if (!store) return false;
|
|
855
|
+
try {
|
|
856
|
+
store.setItem(explainKeyOf(sessionId), on === true ? '1' : '0');
|
|
857
|
+
} catch {
|
|
858
|
+
return false;
|
|
859
|
+
}
|
|
860
|
+
for (const listener of [...explainBus]) {
|
|
861
|
+
try {
|
|
862
|
+
listener();
|
|
863
|
+
} catch {
|
|
864
|
+
// 单个订阅坏掉不影响其它面板。
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
return true;
|
|
868
|
+
};
|
|
869
|
+
const watchExplain = (listener) => {
|
|
870
|
+
explainBus.add(listener);
|
|
871
|
+
return () => explainBus.delete(listener);
|
|
872
|
+
};
|
|
873
|
+
/**
|
|
874
|
+
* 面板只说判断结果,不说内部原因码;条数来自上一次自动召回的回执。
|
|
875
|
+
*/
|
|
876
|
+
const recallLineOf = (receipt) => {
|
|
877
|
+
if (!receipt || typeof receipt !== 'object') return '';
|
|
878
|
+
const cards = Number.isInteger(receipt.cards) ? receipt.cards : 0;
|
|
879
|
+
const matched = Number.isInteger(receipt.matched) ? receipt.matched : 0;
|
|
880
|
+
const related = matched > 0 ? `${matched} 条相关记忆,` : '';
|
|
881
|
+
switch (receipt.reason) {
|
|
882
|
+
case 'hit': return cards > 0 ? `上次自动提醒 ${cards} 条` : '';
|
|
883
|
+
case 'budget': return `上次没提醒,${related}本轮自动提醒已用满`;
|
|
884
|
+
case 'needs_context': return `上次没提醒,${related}适用条件未确认`;
|
|
885
|
+
case 'no_overlap': return '上次没提醒,没有匹配的记忆';
|
|
886
|
+
case 'memory_off': return '上次没提醒,记忆已关闭';
|
|
887
|
+
case 'tools_only': return '上次没提醒,只在你问时检索';
|
|
888
|
+
default: return '';
|
|
889
|
+
}
|
|
890
|
+
};
|
|
773
891
|
/** 功能关闭时视图统一回列表,不保留一个画不出来的页签。 */
|
|
774
892
|
const graphViewOf = (enabled, view) => (enabled === true && view === 'graph' ? 'graph' : 'list');
|
|
775
893
|
const viewModeOf = (display, view) => {
|
|
@@ -803,6 +921,15 @@ window.__ModuleLoader__.load({
|
|
|
803
921
|
return reduced;
|
|
804
922
|
};
|
|
805
923
|
|
|
924
|
+
/** 同标题(去空白后)出现几次;体检与重复标题筛选共用。 */
|
|
925
|
+
const titleCountsOf = (records) => {
|
|
926
|
+
const counts = new Map();
|
|
927
|
+
(Array.isArray(records) ? records : EMPTY).forEach((record) => {
|
|
928
|
+
const title = typeof record.title === 'string' ? record.title.trim() : '';
|
|
929
|
+
if (title) counts.set(title, (counts.get(title) || 0) + 1);
|
|
930
|
+
});
|
|
931
|
+
return counts;
|
|
932
|
+
};
|
|
806
933
|
/** 标签/话题按精确值筛选;只有自由搜索才用包含匹配。 */
|
|
807
934
|
const filterRecords = (records, query, hub) => {
|
|
808
935
|
const rows = Array.isArray(records) ? records : EMPTY;
|
|
@@ -814,6 +941,15 @@ window.__ModuleLoader__.load({
|
|
|
814
941
|
if (kind === 'topic' && value) {
|
|
815
942
|
return rows.filter((record) => record.topicKey === value);
|
|
816
943
|
}
|
|
944
|
+
if (kind === 'duplicate' || kind === 'unread' || kind === 'noWhen') {
|
|
945
|
+
const counts = titleCountsOf(rows);
|
|
946
|
+
return rows.filter((record) => {
|
|
947
|
+
if (kind === 'unread') return !record.lastUsed;
|
|
948
|
+
if (kind === 'noWhen') return !record.when;
|
|
949
|
+
const title = typeof record.title === 'string' ? record.title.trim() : '';
|
|
950
|
+
return Boolean(title) && counts.get(title) > 1;
|
|
951
|
+
});
|
|
952
|
+
}
|
|
817
953
|
const needle = typeof query === 'string' ? query.trim().toLowerCase() : '';
|
|
818
954
|
if (!needle) return rows;
|
|
819
955
|
return rows.filter((record) => {
|
|
@@ -822,6 +958,21 @@ window.__ModuleLoader__.load({
|
|
|
822
958
|
});
|
|
823
959
|
};
|
|
824
960
|
|
|
961
|
+
/** 结构体检:只看现行记录,缺的数据给 null,不猜。 */
|
|
962
|
+
const checkupOf = (records, scopes, globalSelected) => {
|
|
963
|
+
const rows = Array.isArray(records) ? records : EMPTY;
|
|
964
|
+
const counts = titleCountsOf(rows);
|
|
965
|
+
let duplicate = 0;
|
|
966
|
+
counts.forEach((count) => { if (count > 1) duplicate += 1; });
|
|
967
|
+
const globalRow = (Array.isArray(scopes) ? scopes : EMPTY).find((scope) => scope && scope.kind === 'global');
|
|
968
|
+
return {
|
|
969
|
+
duplicate,
|
|
970
|
+
unread: rows.filter((record) => !record.lastUsed).length,
|
|
971
|
+
globalEntries: globalRow && Number.isInteger(globalRow.entries) ? globalRow.entries : null,
|
|
972
|
+
globalMissingWhen: globalSelected ? rows.filter((record) => !record.when).length : null,
|
|
973
|
+
};
|
|
974
|
+
};
|
|
975
|
+
|
|
825
976
|
/** 记忆宫殿:房间固定,槽位固定,换项目只换陈列内容。 */
|
|
826
977
|
const PALACE_PAGE_SIZE = 24;
|
|
827
978
|
const PALACE_PREVIEW = 4;
|
|
@@ -1091,7 +1242,7 @@ window.__ModuleLoader__.load({
|
|
|
1091
1242
|
edges.push({ from, to, type });
|
|
1092
1243
|
};
|
|
1093
1244
|
for (const record of rows) {
|
|
1094
|
-
nodes.push({ id: record.id, kind: 'memory', label: record.title ||
|
|
1245
|
+
nodes.push({ id: record.id, kind: 'memory', label: record.title || '记忆', experimental: record.experimental === true });
|
|
1095
1246
|
}
|
|
1096
1247
|
for (const [tag, count] of tags) {
|
|
1097
1248
|
nodes.push({ id: `tag:${tag}`, kind: 'tag', label: `#${tag}`, count });
|
|
@@ -1490,13 +1641,15 @@ window.__ModuleLoader__.load({
|
|
|
1490
1641
|
},
|
|
1491
1642
|
}));
|
|
1492
1643
|
|
|
1493
|
-
const STAGE = { pending: '候选', trial: '试用', active: '生效', paused: '暂停' };
|
|
1644
|
+
const STAGE = { pending: '候选', trial: '试用', active: '生效', paused: '暂停', legacy: '待复核' };
|
|
1494
1645
|
const STAGE_TONE = {
|
|
1495
1646
|
pending: 'var(--dsw-alias-state-warn-primary)',
|
|
1496
1647
|
trial: 'var(--dsw-alias-brand-primary)',
|
|
1497
1648
|
active: 'var(--dsw-alias-state-success-primary)',
|
|
1498
1649
|
paused: 'var(--dsw-alias-label-secondary)',
|
|
1650
|
+
legacy: 'var(--dsw-alias-state-warn-primary)',
|
|
1499
1651
|
};
|
|
1652
|
+
const stageOf = (rule) => (rule.reviewState === 'legacy_unreviewed' ? 'legacy' : rule.state);
|
|
1500
1653
|
const StagePill = (props) => h('span', {
|
|
1501
1654
|
style: {
|
|
1502
1655
|
flex: '0 0 auto', padding: '0 6px', border: '1px solid var(--dsw-alias-border-l1)',
|
|
@@ -1533,6 +1686,7 @@ window.__ModuleLoader__.load({
|
|
|
1533
1686
|
const usabilityOf = (rule, evolution, memoryOn) => {
|
|
1534
1687
|
if (!memoryOn) return '记忆已关闭,暂不参与回答。';
|
|
1535
1688
|
if (!evolution || evolution.enabled !== true || evolution.effectiveEnabled === false) return '自进化已关闭,暂不参与回答。';
|
|
1689
|
+
if (rule.reviewState === 'legacy_unreviewed') return '所属项目还没确认,暂不参与回答。';
|
|
1536
1690
|
if (rule.state === 'paused') return '已暂停,不参与回答。';
|
|
1537
1691
|
const unresolved = rule.applicability === 'unresolved'
|
|
1538
1692
|
|| (rule.applicability === 'conditional' && (!Array.isArray(rule.conditions) || rule.conditions.length === 0));
|
|
@@ -1589,7 +1743,7 @@ window.__ModuleLoader__.load({
|
|
|
1589
1743
|
color: 'inherit', padding: '4px 8px', cursor: 'pointer', minWidth: 0,
|
|
1590
1744
|
},
|
|
1591
1745
|
},
|
|
1592
|
-
h('span', { style: { fontSize: '12px', lineHeight: '16px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, record.title ||
|
|
1746
|
+
h('span', { style: { fontSize: '12px', lineHeight: '16px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, record.title || '记忆'),
|
|
1593
1747
|
h('span', { style: { ...PANEL_MUTED, display: 'flex', alignItems: 'center', gap: '5px', minWidth: 0, lineHeight: '14px' } },
|
|
1594
1748
|
h('span', null, KIND_LABEL[record.kind] || record.kind),
|
|
1595
1749
|
record.experimental ? h('span', { style: { color: 'var(--dsw-alias-state-warn-primary)' } }, '实验') : null,
|
|
@@ -1616,7 +1770,7 @@ window.__ModuleLoader__.load({
|
|
|
1616
1770
|
},
|
|
1617
1771
|
h('span', { style: { fontSize: '12px', lineHeight: '18px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, rule.title),
|
|
1618
1772
|
h('span', { style: { ...PANEL_MUTED, display: 'flex', alignItems: 'center', gap: '6px', minWidth: 0, lineHeight: '16px' } },
|
|
1619
|
-
h(StagePill, { state: rule
|
|
1773
|
+
h(StagePill, { state: stageOf(rule) }),
|
|
1620
1774
|
h('span', { style: { marginLeft: 'auto', flex: '0 0 auto', fontVariantNumeric: 'tabular-nums' } }, EVO_DATE(rule.updated)),
|
|
1621
1775
|
));
|
|
1622
1776
|
};
|
|
@@ -1655,7 +1809,7 @@ window.__ModuleLoader__.load({
|
|
|
1655
1809
|
? h('div', { style: { display: 'flex', alignItems: 'center', gap: '12px', flexWrap: 'wrap', paddingTop: '3px' } },
|
|
1656
1810
|
rule.state === 'pending' || rule.state === 'trial'
|
|
1657
1811
|
? h(Action, { disabled: !canManage, onClick: () => act('adopt') }, '采纳') : null,
|
|
1658
|
-
rule.state === 'paused'
|
|
1812
|
+
rule.state === 'paused' && rule.reviewState !== 'legacy_unreviewed'
|
|
1659
1813
|
? h(Action, { disabled: !canManage, onClick: () => act('resume') }, '恢复')
|
|
1660
1814
|
: (rule.state === 'trial' || rule.state === 'active'
|
|
1661
1815
|
? h(Action, { disabled: !canManage, onClick: () => act('pause') }, '暂停')
|
|
@@ -1742,7 +1896,7 @@ window.__ModuleLoader__.load({
|
|
|
1742
1896
|
h('div', { style: { display: 'flex', flexDirection: 'column', gap: '4px' } },
|
|
1743
1897
|
h('div', { style: { display: 'flex', alignItems: 'center', gap: '6px' } },
|
|
1744
1898
|
h('span', { style: PANEL_LABEL }, '当前状态'),
|
|
1745
|
-
h(StagePill, { state: rule
|
|
1899
|
+
h(StagePill, { state: stageOf(rule) }),
|
|
1746
1900
|
),
|
|
1747
1901
|
h('div', { style: { fontSize: '12px', lineHeight: '1.55', opacity: 0.82 } }, usabilityOf(rule, props.evolution, props.memoryOn)),
|
|
1748
1902
|
failureState
|
|
@@ -1770,7 +1924,7 @@ window.__ModuleLoader__.load({
|
|
|
1770
1924
|
style: { height: '46px', border: '1px dashed var(--dsw-alias-border-l1)', borderRadius: '8px', opacity: 0.28 },
|
|
1771
1925
|
});
|
|
1772
1926
|
}
|
|
1773
|
-
const title = String(record.title ||
|
|
1927
|
+
const title = String(record.title || '记忆');
|
|
1774
1928
|
return h('button', {
|
|
1775
1929
|
type: 'button', title, 'aria-label': title,
|
|
1776
1930
|
onClick: () => props.onPick(record.id),
|
|
@@ -1948,11 +2102,17 @@ window.__ModuleLoader__.load({
|
|
|
1948
2102
|
const [selectedRule, setSelectedRule] = React.useState('');
|
|
1949
2103
|
const [detail, setDetail] = React.useState(null);
|
|
1950
2104
|
const [detailNote, setDetailNote] = React.useState('');
|
|
2105
|
+
const [editingMemory, setEditingMemory] = React.useState(false);
|
|
2106
|
+
const [editTitle, setEditTitle] = React.useState('');
|
|
2107
|
+
const [editBody, setEditBody] = React.useState('');
|
|
2108
|
+
const [editNote, setEditNote] = React.useState('');
|
|
1951
2109
|
const [busy, setBusy] = React.useState(false);
|
|
1952
2110
|
const [showReason, setShowReason] = React.useState(false);
|
|
1953
2111
|
const [showSettings, setShowSettings] = React.useState(false);
|
|
2112
|
+
const [explainOn, setExplainOn] = React.useState(explainModeOf(sessionId));
|
|
1954
2113
|
const [display, setDisplay] = React.useState({ ...DISPLAY_DEFAULTS });
|
|
1955
2114
|
const [hubFilter, setHubFilter] = React.useState(null);
|
|
2115
|
+
const [checkup, setCheckup] = React.useState(false);
|
|
1956
2116
|
const alive = React.useRef(true);
|
|
1957
2117
|
const generation = React.useRef(0);
|
|
1958
2118
|
const scopeRef = React.useRef('');
|
|
@@ -1966,6 +2126,8 @@ window.__ModuleLoader__.load({
|
|
|
1966
2126
|
const load = (scopeKey = '', focusRule = '') => {
|
|
1967
2127
|
const request = generation.current + 1;
|
|
1968
2128
|
generation.current = request;
|
|
2129
|
+
/** 用户选的项目立刻算数:晚到的保存/开关回执不能再把界面拉回旧范围。 */
|
|
2130
|
+
if (scopeKey) scopeRef.current = scopeKey;
|
|
1969
2131
|
setLoading(true);
|
|
1970
2132
|
setFailed(false);
|
|
1971
2133
|
setNote('');
|
|
@@ -2023,6 +2185,8 @@ window.__ModuleLoader__.load({
|
|
|
2023
2185
|
}, []);
|
|
2024
2186
|
|
|
2025
2187
|
React.useEffect(() => {
|
|
2188
|
+
setEditingMemory(false);
|
|
2189
|
+
setEditNote('');
|
|
2026
2190
|
if (!selectedMemory || !data) { setDetail(null); setDetailNote(''); return undefined; }
|
|
2027
2191
|
let live = true;
|
|
2028
2192
|
setDetail(null);
|
|
@@ -2088,7 +2252,7 @@ window.__ModuleLoader__.load({
|
|
|
2088
2252
|
} else if (activityCode === 'scope_unconfirmed' || activityCode === 'unknown_scope') {
|
|
2089
2253
|
activityText = '暂未整理:适用范围待确认';
|
|
2090
2254
|
} else if (activityCode === 'quota' || activityCode === 'exhausted') {
|
|
2091
|
-
activityText = '
|
|
2255
|
+
activityText = '这轮先整理这些,下一条消息接着整理';
|
|
2092
2256
|
} else if (activityCode === 'failed' || activityCode === 'error') {
|
|
2093
2257
|
activityText = staleLearning ? '这轮整理没采用' : '最近整理失败,可查看原因';
|
|
2094
2258
|
} else if (activityCode === 'waiting' || activityCode === 'idle' || activityCode === 'empty') {
|
|
@@ -2109,6 +2273,25 @@ window.__ModuleLoader__.load({
|
|
|
2109
2273
|
return rules.filter((rule) => rule.state === stage);
|
|
2110
2274
|
}, [rules, stage]);
|
|
2111
2275
|
const selectedRuleRow = selectedRule ? rules.find((rule) => rule.id === selectedRule) : null;
|
|
2276
|
+
const checkupData = checkupOf(records, data && data.scopes, globalView);
|
|
2277
|
+
const globalScopeRow = (data && Array.isArray(data.scopes) ? data.scopes : EMPTY).find((scope) => scope && scope.kind === 'global');
|
|
2278
|
+
const checkupJump = (hub) => { setCheckup(false); setTab('memory'); setQuery(''); setHubFilter(hub); };
|
|
2279
|
+
const checkupJumpGlobal = (hub) => {
|
|
2280
|
+
setCheckup(false); setQuery(''); setHubFilter(hub || null);
|
|
2281
|
+
if (globalScopeRow) load(globalScopeRow.key);
|
|
2282
|
+
else setTab('memory');
|
|
2283
|
+
};
|
|
2284
|
+
const checkupRowOf = (key, label, value, unit, onClick) => h('button', {
|
|
2285
|
+
key, type: 'button', onClick,
|
|
2286
|
+
style: {
|
|
2287
|
+
display: 'flex', alignItems: 'center', gap: '8px', width: '100%', minWidth: 0,
|
|
2288
|
+
padding: '7px 9px', border: 'none', background: 'transparent', color: 'inherit',
|
|
2289
|
+
font: 'inherit', textAlign: 'left', cursor: 'pointer',
|
|
2290
|
+
borderBottom: '1px solid var(--dsw-alias-border-l1)',
|
|
2291
|
+
},
|
|
2292
|
+
},
|
|
2293
|
+
h('span', { style: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, label),
|
|
2294
|
+
h('span', { style: { marginLeft: 'auto', flex: '0 0 auto', fontVariantNumeric: 'tabular-nums', opacity: 0.72 } }, value === null || value === undefined ? '—' : `${value} ${unit}`));
|
|
2112
2295
|
const detailRecord = selectedMemory ? records.find((record) => record.id === selectedMemory) : null;
|
|
2113
2296
|
|
|
2114
2297
|
const onPickNode = (node) => {
|
|
@@ -2149,18 +2332,64 @@ window.__ModuleLoader__.load({
|
|
|
2149
2332
|
};
|
|
2150
2333
|
if (learningAction === 'pause') payload.ids = [selectedRuleRow.id];
|
|
2151
2334
|
post('/lite-memory/evolution', payload).then((result) => {
|
|
2152
|
-
if (!alive.current) return;
|
|
2335
|
+
if (!alive.current || (selectedKey && scopeRef.current !== selectedKey)) return;
|
|
2153
2336
|
setBusy(false);
|
|
2154
2337
|
if (succeeded(result)) load(selectedKey, selectedRuleRow.id);
|
|
2155
2338
|
else setNote(failure(result, '这次没有改动'));
|
|
2156
2339
|
});
|
|
2157
2340
|
};
|
|
2158
2341
|
|
|
2342
|
+
/** 普通记忆的正文由人直接改:带读到的版本写,冲突就提示刷新,不覆盖别处的修改。 */
|
|
2343
|
+
const saveMemoryEdit = (record) => {
|
|
2344
|
+
if (busy || !canManage || !record || !record.id) return;
|
|
2345
|
+
const title = editTitle.trim();
|
|
2346
|
+
const text = editBody.trim();
|
|
2347
|
+
if (!title || !text) return;
|
|
2348
|
+
setBusy(true);
|
|
2349
|
+
setEditNote('');
|
|
2350
|
+
const payload = { sessionId, scopeKey: selectedKey, id: record.id, title, body: text, expectedRevision: record.revision };
|
|
2351
|
+
post('/lite-memory/entry', payload).then((result) => {
|
|
2352
|
+
if (!alive.current || (selectedKey && scopeRef.current !== selectedKey)) return;
|
|
2353
|
+
setBusy(false);
|
|
2354
|
+
if (succeeded(result)) {
|
|
2355
|
+
const nextTitle = result.body && typeof result.body.title === 'string' ? result.body.title : title;
|
|
2356
|
+
const nextRevision = result.body && Number.isInteger(result.body.revision) ? result.body.revision : record.revision;
|
|
2357
|
+
setEditingMemory(false);
|
|
2358
|
+
setDetail((prev) => (prev ? { ...prev, title: nextTitle, body: text, currentBody: text, revision: nextRevision } : prev));
|
|
2359
|
+
setData((prev) => {
|
|
2360
|
+
if (!prev || !prev.selected || !Array.isArray(prev.selected.records)) return prev;
|
|
2361
|
+
const records = prev.selected.records.map((row) => (row.id === record.id ? { ...row, title: nextTitle, revision: nextRevision } : row));
|
|
2362
|
+
return { ...prev, selected: { ...prev.selected, records } };
|
|
2363
|
+
});
|
|
2364
|
+
setEditNote('');
|
|
2365
|
+
} else {
|
|
2366
|
+
setEditNote(failure(result, '这次没有改动'));
|
|
2367
|
+
/**
|
|
2368
|
+
* 版本过期不是失败终点:把最新正文和版本取回来,草稿留着,
|
|
2369
|
+
* 人确认后再按一次保存就写在最新版本上。
|
|
2370
|
+
*/
|
|
2371
|
+
if (result.status === 409 && result.body && result.body.retry) {
|
|
2372
|
+
post('/lite-memory/body', { sessionId, id: record.id, scopeKey: selectedKey }).then((fresh) => {
|
|
2373
|
+
if (!alive.current || (selectedKey && scopeRef.current !== selectedKey)
|
|
2374
|
+
|| !fresh.ok || !fresh.body || !fresh.body.ok || fresh.body.status === 'gone') return;
|
|
2375
|
+
const revision = Number.isInteger(fresh.body.revision) ? fresh.body.revision : 0;
|
|
2376
|
+
setDetail(fresh.body);
|
|
2377
|
+
setData((prev) => {
|
|
2378
|
+
if (!prev || !prev.selected || !Array.isArray(prev.selected.records)) return prev;
|
|
2379
|
+
const records = prev.selected.records.map((row) => (row.id === record.id ? { ...row, revision: revision || row.revision } : row));
|
|
2380
|
+
return { ...prev, selected: { ...prev.selected, records } };
|
|
2381
|
+
});
|
|
2382
|
+
});
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
});
|
|
2386
|
+
};
|
|
2387
|
+
|
|
2159
2388
|
const toggleControl = () => {
|
|
2160
2389
|
if (busy || !canToggle) return;
|
|
2161
2390
|
setBusy(true);
|
|
2162
2391
|
post('/lite-memory/control', { sessionId, enabled: !memoryOn, operationId: newOperationId() }).then((result) => {
|
|
2163
|
-
if (!alive.current) return;
|
|
2392
|
+
if (!alive.current || (selectedKey && scopeRef.current !== selectedKey)) return;
|
|
2164
2393
|
setBusy(false);
|
|
2165
2394
|
if (succeeded(result)) load(selectedKey);
|
|
2166
2395
|
else setNote(failure(result, '开关没有改动'));
|
|
@@ -2171,7 +2400,7 @@ window.__ModuleLoader__.load({
|
|
|
2171
2400
|
if (busy || !evolution || !canToggle || !memoryOn) return;
|
|
2172
2401
|
setBusy(true);
|
|
2173
2402
|
post('/lite-memory/evolution', { sessionId, enabled: evolution.enabled !== true, operationId: newOperationId() }).then((result) => {
|
|
2174
|
-
if (!alive.current) return;
|
|
2403
|
+
if (!alive.current || (selectedKey && scopeRef.current !== selectedKey)) return;
|
|
2175
2404
|
setBusy(false);
|
|
2176
2405
|
if (succeeded(result)) load(selectedKey);
|
|
2177
2406
|
else setNote(failure(result, '开关没有改动'));
|
|
@@ -2219,6 +2448,18 @@ window.__ModuleLoader__.load({
|
|
|
2219
2448
|
selectedKey ? () => setDisplayFlag(key, display[key] !== true) : undefined,
|
|
2220
2449
|
);
|
|
2221
2450
|
|
|
2451
|
+
const explainToggle = () => switchItem(
|
|
2452
|
+
'解释模式',
|
|
2453
|
+
explainOn ? '零命中时说明原因' : '零命中时不出提示',
|
|
2454
|
+
explainOn,
|
|
2455
|
+
!sessionId,
|
|
2456
|
+
sessionId ? () => {
|
|
2457
|
+
const next = explainOn !== true;
|
|
2458
|
+
setExplainOn(next);
|
|
2459
|
+
setExplainMode(sessionId, next);
|
|
2460
|
+
} : undefined,
|
|
2461
|
+
);
|
|
2462
|
+
|
|
2222
2463
|
const switchItem = (label, hint, on, disabled, onClick) => h('div', {
|
|
2223
2464
|
style: { display: 'flex', flexDirection: 'column', gap: '1px', minWidth: 0, opacity: disabled && !on ? 0.55 : 1 },
|
|
2224
2465
|
},
|
|
@@ -2228,6 +2469,7 @@ window.__ModuleLoader__.load({
|
|
|
2228
2469
|
),
|
|
2229
2470
|
hint ? h('span', { style: { ...PANEL_MUTED, lineHeight: '16px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, hint) : null);
|
|
2230
2471
|
const evolutionOn = Boolean(memoryOn && evolution && evolution.enabled === true);
|
|
2472
|
+
const recallLine = recallLineOf(current && current.recall);
|
|
2231
2473
|
const switchBar = h('div', {
|
|
2232
2474
|
style: { display: 'flex', flexDirection: 'column', gap: '3px', padding: '7px 18px 8px', borderBottom: '1px solid var(--dsw-alias-border-l1)', flex: '0 0 auto' },
|
|
2233
2475
|
},
|
|
@@ -2238,9 +2480,11 @@ window.__ModuleLoader__.load({
|
|
|
2238
2480
|
switchItem('自进化', evolutionOn ? '实验功能,会增加 token 消耗' : '从反馈里整理经验', evolutionOn, busy || !evolution || !canToggle || !memoryOn, toggleEvolution), // ui-copy-ignore 用户指定文案
|
|
2239
2481
|
showSettings ? displayToggle('启用图谱', 'graph') : null,
|
|
2240
2482
|
showSettings ? displayToggle('启用记忆宫殿', 'palace') : null,
|
|
2483
|
+
showSettings ? explainToggle() : null,
|
|
2241
2484
|
),
|
|
2242
2485
|
!globalView && !canToggle ? h('div', { style: { ...PANEL_MUTED, lineHeight: '18px' } }, '只读,切换回当前项目后可修改') : null,
|
|
2243
2486
|
note ? h('div', { style: { ...PANEL_MUTED, lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary)' } }, note) : null,
|
|
2487
|
+
!globalView && recallLine ? h('div', { style: { ...PANEL_MUTED, lineHeight: '18px' } }, recallLine) : null,
|
|
2244
2488
|
);
|
|
2245
2489
|
|
|
2246
2490
|
const switchTab = (key) => {
|
|
@@ -2278,6 +2522,25 @@ window.__ModuleLoader__.load({
|
|
|
2278
2522
|
}));
|
|
2279
2523
|
|
|
2280
2524
|
const detailProcessing = detail ? processingStatusOf(detail) : '';
|
|
2525
|
+
const memoryEditor = editingMemory && detailRecord
|
|
2526
|
+
? h('div', { style: { display: 'flex', flexDirection: 'column', gap: '8px' } },
|
|
2527
|
+
h(Field, { label: '标题', value: editTitle, onChange: setEditTitle }),
|
|
2528
|
+
h('label', { style: { display: 'flex', flexDirection: 'column', gap: '4px' } },
|
|
2529
|
+
h('span', { style: PANEL_LABEL }, '经验'),
|
|
2530
|
+
h('textarea', {
|
|
2531
|
+
value: editBody, onChange: (event) => setEditBody(event.target.value), 'aria-label': '经验',
|
|
2532
|
+
rows: 8,
|
|
2533
|
+
style: { ...PANEL_INPUT, height: 'auto', minHeight: '150px', padding: '7px 9px', resize: 'vertical', lineHeight: '1.6' },
|
|
2534
|
+
}),
|
|
2535
|
+
),
|
|
2536
|
+
editNote ? h('div', { style: { ...PANEL_MUTED, color: 'var(--dsw-alias-label-error)' } }, editNote) : null,
|
|
2537
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: '12px', flexWrap: 'wrap' } },
|
|
2538
|
+
h(Action, { disabled: busy || !editTitle.trim() || !editBody.trim(), onClick: () => saveMemoryEdit(detailRecord) }, '保存'),
|
|
2539
|
+
h(Action, { disabled: busy, onClick: () => { setEditingMemory(false); setEditNote(''); } }, '取消'),
|
|
2540
|
+
),
|
|
2541
|
+
)
|
|
2542
|
+
: null;
|
|
2543
|
+
|
|
2281
2544
|
const memoryDetail = selectedMemory
|
|
2282
2545
|
? h('div', { style: { ...PANEL_CARD, minHeight: 0, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: '6px' } },
|
|
2283
2546
|
detail
|
|
@@ -2290,8 +2553,22 @@ window.__ModuleLoader__.load({
|
|
|
2290
2553
|
Array.isArray(detailRecord && detailRecord.tags) && detailRecord.tags.length
|
|
2291
2554
|
? h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '4px' } },
|
|
2292
2555
|
detailRecord.tags.slice(0, 6).map((tag) => h('span', { key: tag, style: { ...PANEL_MUTED, padding: '0 6px', border: '1px solid var(--dsw-alias-border-l1)', borderRadius: '999px' } }, `#${tag}`))) : null,
|
|
2293
|
-
|
|
2294
|
-
h(
|
|
2556
|
+
memoryEditor,
|
|
2557
|
+
editingMemory ? null : h('div', { style: { fontSize: '12px', lineHeight: '1.65', whiteSpace: 'pre-wrap', wordBreak: 'break-word' } }, currentBodyOf(detail) || '没有正文'),
|
|
2558
|
+
editingMemory ? null : h(DetailExtras, { payload: detail }),
|
|
2559
|
+
!editingMemory && detailRecord && canManage
|
|
2560
|
+
? h('div', { style: { display: 'flex', alignItems: 'center', gap: '12px', flexWrap: 'wrap', paddingTop: '2px' } },
|
|
2561
|
+
h(Action, {
|
|
2562
|
+
disabled: busy,
|
|
2563
|
+
onClick: () => {
|
|
2564
|
+
setEditTitle(detailRecord.title || detail.title || '');
|
|
2565
|
+
setEditBody(currentBodyOf(detail) || '');
|
|
2566
|
+
setEditNote('');
|
|
2567
|
+
setEditingMemory(true);
|
|
2568
|
+
},
|
|
2569
|
+
}, '修改'),
|
|
2570
|
+
)
|
|
2571
|
+
: null,
|
|
2295
2572
|
)
|
|
2296
2573
|
: h('div', { style: PANEL_MUTED }, detailNote || '读取中…'),
|
|
2297
2574
|
)
|
|
@@ -2388,7 +2665,15 @@ window.__ModuleLoader__.load({
|
|
|
2388
2665
|
fontVariantNumeric: 'tabular-nums',
|
|
2389
2666
|
},
|
|
2390
2667
|
}, `${label} ${countText(key)}`)),
|
|
2391
|
-
h('span', { style: { marginLeft: 'auto'
|
|
2668
|
+
h('span', { style: { marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: '8px' } },
|
|
2669
|
+
h('button', {
|
|
2670
|
+
type: 'button', 'aria-pressed': checkup, onClick: () => setCheckup((value) => !value),
|
|
2671
|
+
style: {
|
|
2672
|
+
...PANEL_LINK, height: '30px', opacity: checkup ? 0.92 : 0.68,
|
|
2673
|
+
borderBottom: checkup ? '1px solid currentColor' : '1px solid transparent',
|
|
2674
|
+
},
|
|
2675
|
+
}, '结构体检'),
|
|
2676
|
+
display.graph ? viewToggleOf(evolutionView, setEvolutionView, '自进化视图') : null),
|
|
2392
2677
|
);
|
|
2393
2678
|
|
|
2394
2679
|
const stageLabel = (stageFilters.find(([key]) => key === stage) || ['all', '全部'])[1];
|
|
@@ -2439,6 +2724,18 @@ window.__ModuleLoader__.load({
|
|
|
2439
2724
|
emptyText: evolutionEmptyText, ariaLabel: '经验关系图',
|
|
2440
2725
|
legend: '实心 经验 · 空心 条件 · 虚线 共用条件 · 点经验查看',
|
|
2441
2726
|
}));
|
|
2727
|
+
const evolutionCheckupPane = h('div', {
|
|
2728
|
+
style: {
|
|
2729
|
+
...PANEL_CARD, padding: '4px', flex: '0 0 auto', minHeight: 0, overflowY: 'auto',
|
|
2730
|
+
display: 'flex', flexDirection: 'column', gap: 0,
|
|
2731
|
+
},
|
|
2732
|
+
},
|
|
2733
|
+
checkupRowOf('duplicate', '重复标题', checkupData.duplicate, '组', () => checkupJump({ kind: 'duplicate', display: '重复标题' })),
|
|
2734
|
+
checkupRowOf('global', '全局条目超 200', checkupData.globalEntries, '条', () => checkupJumpGlobal()),
|
|
2735
|
+
checkupRowOf('globalWhen', '全局缺适用条件', checkupData.globalMissingWhen, '条',
|
|
2736
|
+
() => checkupJumpGlobal({ kind: 'noWhen', display: '全局缺适用条件' })),
|
|
2737
|
+
checkupRowOf('unread', '从未读过', checkupData.unread, '条', () => checkupJump({ kind: 'unread', display: '从未读过' })));
|
|
2738
|
+
|
|
2442
2739
|
const evolutionTab = evolutionFailed ? evolutionFailed : h('div', { role: 'tabpanel', 'aria-label': '自进化', style: { flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', gap: '9px' } },
|
|
2443
2740
|
h('div', { style: { ...PANEL_CARD, padding: '9px 11px', display: 'flex', alignItems: 'flex-start', gap: '8px', flex: '0 0 auto' } },
|
|
2444
2741
|
h('span', { style: { flex: '0 0 auto', width: '6px', height: '6px', marginTop: '5px', borderRadius: '999px', background: effectiveEnabled ? 'var(--dsw-alias-brand-primary)' : 'var(--dsw-alias-label-secondary)', opacity: 0.8 } }),
|
|
@@ -2456,11 +2753,11 @@ window.__ModuleLoader__.load({
|
|
|
2456
2753
|
h('div', {
|
|
2457
2754
|
style: {
|
|
2458
2755
|
flex: 1, minHeight: 0, display: 'grid', gap: '10px',
|
|
2459
|
-
gridTemplateColumns: selectedRuleRow ? 'minmax(0, 1fr) minmax(280px, 340px)' : 'minmax(0, 1fr)',
|
|
2756
|
+
gridTemplateColumns: !checkup && selectedRuleRow ? 'minmax(0, 1fr) minmax(280px, 340px)' : 'minmax(0, 1fr)',
|
|
2460
2757
|
},
|
|
2461
2758
|
},
|
|
2462
|
-
evolutionViewActive === 'graph' ? evolutionGraphPane : evolutionListPane,
|
|
2463
|
-
evolutionDetailPane),
|
|
2759
|
+
checkup ? evolutionCheckupPane : (evolutionViewActive === 'graph' ? evolutionGraphPane : evolutionListPane),
|
|
2760
|
+
checkup ? null : evolutionDetailPane),
|
|
2464
2761
|
);
|
|
2465
2762
|
|
|
2466
2763
|
const palaceLoose = activeTab === 'memory' && viewModeOf(display, memoryView) === 'palace' && !selectedMemory;
|
|
@@ -2528,12 +2825,14 @@ window.__ModuleLoader__.load({
|
|
|
2528
2825
|
update: list.filter((item) => item.exposure === 'update'),
|
|
2529
2826
|
};
|
|
2530
2827
|
}, [refs]);
|
|
2828
|
+
const [explainOn, setExplainOn] = React.useState(explainModeOf(sessionId));
|
|
2829
|
+
React.useEffect(() => watchExplain(() => setExplainOn(explainModeOf(sessionId))), [sessionId]);
|
|
2531
2830
|
const noTriggerReason = typeof props.noTriggerReason === 'string' && props.noTriggerReason
|
|
2532
2831
|
? props.noTriggerReason
|
|
2533
2832
|
: (typeof refs.noTriggerReason === 'string' ? refs.noTriggerReason : '');
|
|
2534
2833
|
if (!sessionId && refs.length === 0) return null;
|
|
2535
2834
|
if (groups.found.length === 0 && groups.provided.length === 0 && groups.update.length === 0) {
|
|
2536
|
-
return noTriggerReason
|
|
2835
|
+
return explainOn && noTriggerReason
|
|
2537
2836
|
? h('div', { style: TAIL }, h(RefSection, { label: '搜索命中', tone: 'found', items: EMPTY, sessionId, turnKey: turn, noTriggerReason }))
|
|
2538
2837
|
: null;
|
|
2539
2838
|
}
|
|
@@ -2551,7 +2850,7 @@ window.__ModuleLoader__.load({
|
|
|
2551
2850
|
|
|
2552
2851
|
// 测试缝:Node 里验引用投影,浏览器里没有任何副作用。
|
|
2553
2852
|
if (typeof globalThis.__LITE_MEMORY_TEST__ === 'function') {
|
|
2554
|
-
globalThis.__LITE_MEMORY_TEST__({ metaOf, citationOf, citationsOfMeta, citationsOfTurn, dedupe, undoRefOf, graphOf, ruleGraphOf, processingStatusOf, currentBodyOf, historyDiffsOf, MemoryPanel, MemoryGraph, MemoryPalace, TurnMemory, RefSection, displayKeyOf, readDisplaySettings, writeDisplaySettings, graphViewOf, viewModeOf, filterRecords, projectionOf, failureStateOf, evidenceTextOf, evidenceMetaOf, palaceRoomsOf, palaceRoomPageOf, palacePageWindow, palaceToggleOf, palaceMotionOf, palaceRoomIdOf });
|
|
2853
|
+
globalThis.__LITE_MEMORY_TEST__({ metaOf, citationOf, citationsOfMeta, citationsOfTurn, dedupe, undoRefOf, graphOf, ruleGraphOf, processingStatusOf, currentBodyOf, historyDiffsOf, MemoryPanel, MemoryGraph, MemoryPalace, TurnMemory, RefSection, displayKeyOf, readDisplaySettings, writeDisplaySettings, graphViewOf, viewModeOf, filterRecords, projectionOf, failureStateOf, evidenceTextOf, evidenceMetaOf, palaceRoomsOf, palaceRoomPageOf, palacePageWindow, palaceToggleOf, palaceMotionOf, palaceRoomIdOf, refMetaOf, explainKeyOf, explainModeOf, setExplainMode, recallLineOf, agoLabel, checkupOf, titleCountsOf, rowKeyOf, rowKeepFlags, Row });
|
|
2555
2854
|
}
|
|
2556
2855
|
|
|
2557
2856
|
return {
|