dsh-selfnote 0.14.0 → 0.14.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 +3 -3
- package/lib/client.js +318 -8
- package/lib/index.js +7 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
|
-
<img src="https://img.shields.io/badge/version-0.14.
|
|
13
|
+
<img src="https://img.shields.io/badge/version-0.14.1-4f46e5" alt="version">
|
|
14
14
|
<img src="https://img.shields.io/badge/license-MIT-16a34a" alt="license">
|
|
15
15
|
<img src="https://img.shields.io/badge/node-%3E%3D20.13-339933" alt="node">
|
|
16
|
-
<img src="https://img.shields.io/badge/tests-
|
|
16
|
+
<img src="https://img.shields.io/badge/tests-290%20passed-0ea5e9" alt="tests">
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
19
|
---
|
|
@@ -528,7 +528,7 @@ L2 为空时那句指路句也不发(不能指一条空路)。L2 有内容
|
|
|
528
528
|
## 开发
|
|
529
529
|
|
|
530
530
|
```bash
|
|
531
|
-
node --run test #
|
|
531
|
+
node --run test # 290 项,21 个文件,无外部依赖
|
|
532
532
|
node --run test:accept # 12 项:真实 HTTP + 36 进程并发写
|
|
533
533
|
```
|
|
534
534
|
|
package/lib/client.js
CHANGED
|
@@ -178,6 +178,28 @@ window.__ModuleLoader__.load({
|
|
|
178
178
|
var ruleDraftTuple = useState('')
|
|
179
179
|
var ruleDraft = ruleDraftTuple[0]
|
|
180
180
|
var setRuleDraft = ruleDraftTuple[1]
|
|
181
|
+
// 退役也要两步:退役不删数据,但退役之后它就不进注入了,误点一下等于凭空少一条
|
|
182
|
+
var pendingRetireTuple = useState(null)
|
|
183
|
+
var pendingRetire = pendingRetireTuple[0]
|
|
184
|
+
var setPendingRetire = pendingRetireTuple[1]
|
|
185
|
+
// 「已退役」默认收起:正常翻记忆时不该被一堆旧结论挡路
|
|
186
|
+
var showRetiredTuple = useState(false)
|
|
187
|
+
var showRetired = showRetiredTuple[0]
|
|
188
|
+
var setShowRetired = showRetiredTuple[1]
|
|
189
|
+
// 半衰期与画像上限:以前只能在 config.json 里手改,界面上看不见也就没人调
|
|
190
|
+
var halfLifeTuple = useState('')
|
|
191
|
+
var halfLife = halfLifeTuple[0]
|
|
192
|
+
var setHalfLife = halfLifeTuple[1]
|
|
193
|
+
var profileMaxTuple = useState('')
|
|
194
|
+
var profileMax = profileMaxTuple[0]
|
|
195
|
+
var setProfileMax = profileMaxTuple[1]
|
|
196
|
+
// 纠偏证据一页只给 30 条:后面还有就用「加载更多」翻,翻过的攒在 moreEvidence 里
|
|
197
|
+
var moreEvidenceTuple = useState([])
|
|
198
|
+
var moreEvidence = moreEvidenceTuple[0]
|
|
199
|
+
var setMoreEvidence = moreEvidenceTuple[1]
|
|
200
|
+
var moreCursorTuple = useState(null)
|
|
201
|
+
var moreCursor = moreCursorTuple[0]
|
|
202
|
+
var setMoreCursor = moreCursorTuple[1]
|
|
181
203
|
|
|
182
204
|
function apply(data) {
|
|
183
205
|
setState(data)
|
|
@@ -189,9 +211,14 @@ window.__ModuleLoader__.load({
|
|
|
189
211
|
setNoteGuide(typeof data.noteGuide === 'string' ? data.noteGuide : '')
|
|
190
212
|
setHeading(typeof data.heading === 'string' ? data.heading : '')
|
|
191
213
|
setTempTtl(String(data.tempTtlDays === undefined ? 14 : data.tempTtlDays))
|
|
214
|
+
setHalfLife(String(data.halfLifeDays === undefined ? 30 : data.halfLifeDays))
|
|
215
|
+
setProfileMax(String(data.profile && data.profile.maxChars !== undefined ? data.profile.maxChars : 110))
|
|
216
|
+
setMoreEvidence([])
|
|
217
|
+
setMoreCursor(null)
|
|
192
218
|
// 每次拿到新状态,列表下标都可能整体挪位:正在改/正等确认的那条已经没意义了
|
|
193
219
|
setEditing(null)
|
|
194
220
|
setPendingDelete(null)
|
|
221
|
+
setPendingRetire(null)
|
|
195
222
|
setFactEdit(null)
|
|
196
223
|
}
|
|
197
224
|
|
|
@@ -225,6 +252,28 @@ window.__ModuleLoader__.load({
|
|
|
225
252
|
}
|
|
226
253
|
}, [confirming])
|
|
227
254
|
|
|
255
|
+
// 「真的删」和「真的退役」跟「真的清空」一个口径:武装 5 秒,走开就自动放下。
|
|
256
|
+
// 之前只有清空带定时器,删自述是点开就永久武装 —— 点了「删」犹豫一下、切个页签回来再点同一格,就真删了。
|
|
257
|
+
useEffect(function () {
|
|
258
|
+
if (pendingDelete === null) return undefined
|
|
259
|
+
var timer = setTimeout(function () {
|
|
260
|
+
setPendingDelete(null)
|
|
261
|
+
}, 5000)
|
|
262
|
+
return function () {
|
|
263
|
+
clearTimeout(timer)
|
|
264
|
+
}
|
|
265
|
+
}, [pendingDelete])
|
|
266
|
+
|
|
267
|
+
useEffect(function () {
|
|
268
|
+
if (pendingRetire === null) return undefined
|
|
269
|
+
var timer = setTimeout(function () {
|
|
270
|
+
setPendingRetire(null)
|
|
271
|
+
}, 5000)
|
|
272
|
+
return function () {
|
|
273
|
+
clearTimeout(timer)
|
|
274
|
+
}
|
|
275
|
+
}, [pendingRetire])
|
|
276
|
+
|
|
228
277
|
function runDoctor() {
|
|
229
278
|
setBusy('doctor')
|
|
230
279
|
apiGet('/doctor')
|
|
@@ -286,6 +335,76 @@ window.__ModuleLoader__.load({
|
|
|
286
335
|
})
|
|
287
336
|
}
|
|
288
337
|
|
|
338
|
+
// 半衰期与画像上限走 /config;服务端会校验范围,越界就 400 把原因带回来
|
|
339
|
+
function saveHalfLife() {
|
|
340
|
+
setBusy('config')
|
|
341
|
+
apiPost('/config', { halfLifeDays: Number(halfLife), profileMaxChars: Number(profileMax) })
|
|
342
|
+
.then(function (data) {
|
|
343
|
+
apply(data)
|
|
344
|
+
setNotice({ tone: 'ok', text: '已保存:结论按 ' + data.halfLifeDays + ' 天衰减,画像上限 ' + (data.profile && data.profile.maxChars) + ' 字。' })
|
|
345
|
+
})
|
|
346
|
+
.catch(function (error) {
|
|
347
|
+
setNotice({ tone: 'warn', text: '保存失败:' + error.message })
|
|
348
|
+
})
|
|
349
|
+
.then(function () {
|
|
350
|
+
setBusy('')
|
|
351
|
+
})
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 索引开关:启用前服务端会自己检查「在盘上 + 没过期」,不满足就 400,把原话显示出来
|
|
355
|
+
function toggleIndex(next) {
|
|
356
|
+
setBusy('index')
|
|
357
|
+
apiPost('/index', { action: next ? 'enable' : 'disable' })
|
|
358
|
+
.then(function (data) {
|
|
359
|
+
apply(data)
|
|
360
|
+
setNotice({ tone: 'ok', text: next ? '索引已启用:下一回合起检索先走索引。' : '索引已关闭:检索退回完整扫描。' })
|
|
361
|
+
})
|
|
362
|
+
.catch(function (error) {
|
|
363
|
+
setNotice({ tone: 'warn', text: next ? '启用失败:' + error.message : '关闭失败:' + error.message })
|
|
364
|
+
})
|
|
365
|
+
.then(function () {
|
|
366
|
+
setBusy('')
|
|
367
|
+
})
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// 翻页:服务端按 (at, id) 给游标,翻过的追加在后面,已勾选的不会因为重新渲染丢掉
|
|
371
|
+
function loadMoreEvidence() {
|
|
372
|
+
var cursor = moreCursor !== null ? moreCursor : state && state.corrections ? state.corrections.nextCursor : null
|
|
373
|
+
if (!cursor) return
|
|
374
|
+
setBusy('corrections')
|
|
375
|
+
apiGet('/corrections?limit=30&cursor=' + encodeURIComponent(cursor))
|
|
376
|
+
.then(function (data) {
|
|
377
|
+
setMoreEvidence(moreEvidence.concat(data.items || []))
|
|
378
|
+
setMoreCursor(data.nextCursor || '')
|
|
379
|
+
if (!data.nextCursor) setNotice({ tone: 'ok', text: '证据已经翻到底了。' })
|
|
380
|
+
})
|
|
381
|
+
.catch(function (error) {
|
|
382
|
+
setNotice({ tone: 'warn', text: '翻页失败:' + error.message })
|
|
383
|
+
})
|
|
384
|
+
.then(function () {
|
|
385
|
+
setBusy('')
|
|
386
|
+
})
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// 丢弃:这些原话连规则都不是,只是记录,丢掉不该有心理负担
|
|
390
|
+
function discardPicked() {
|
|
391
|
+
var ids = pickedEvidence.slice()
|
|
392
|
+
if (ids.length === 0) return
|
|
393
|
+
setBusy('corrections')
|
|
394
|
+
apiPost('/corrections/discard', { ids: ids })
|
|
395
|
+
.then(function (data) {
|
|
396
|
+
apply(data)
|
|
397
|
+
setPickedEvidence([])
|
|
398
|
+
setNotice({ tone: 'ok', text: '已丢弃 ' + ids.length + ' 条证据(它们不会再来烦你)。' })
|
|
399
|
+
})
|
|
400
|
+
.catch(function (error) {
|
|
401
|
+
setNotice({ tone: 'warn', text: '丢弃失败:' + error.message })
|
|
402
|
+
})
|
|
403
|
+
.then(function () {
|
|
404
|
+
setBusy('')
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
|
|
289
408
|
function saveHeading() {
|
|
290
409
|
setBusy('heading')
|
|
291
410
|
apiPost('/config', { heading: heading })
|
|
@@ -591,7 +710,15 @@ window.__ModuleLoader__.load({
|
|
|
591
710
|
apiPost('/fact', { id: id, status: status })
|
|
592
711
|
.then(function (data) {
|
|
593
712
|
apply(data)
|
|
594
|
-
setNotice({
|
|
713
|
+
setNotice({
|
|
714
|
+
tone: 'ok',
|
|
715
|
+
text:
|
|
716
|
+
status === 'approved'
|
|
717
|
+
? '已采纳(原来的退役也一并恢复),下一回合进注入。'
|
|
718
|
+
: status === 'retired'
|
|
719
|
+
? '已退役:不再进注入,要它回来去「已退役」里点恢复。'
|
|
720
|
+
: '已处理。',
|
|
721
|
+
})
|
|
595
722
|
})
|
|
596
723
|
.catch(function (error) {
|
|
597
724
|
setNotice({ tone: 'warn', text: '没改成:' + error.message })
|
|
@@ -601,6 +728,17 @@ window.__ModuleLoader__.load({
|
|
|
601
728
|
})
|
|
602
729
|
}
|
|
603
730
|
|
|
731
|
+
// 退役两步:第一次只武装,第二次才发请求。数据留在 facts.jsonl 里,随时能恢复。
|
|
732
|
+
function retireFact(id) {
|
|
733
|
+
if (pendingRetire !== id) {
|
|
734
|
+
setPendingRetire(id)
|
|
735
|
+
setNotice({ tone: 'info', text: '再点一次「真的退役」——它不进注入了,但还在「已退役」里,随时能恢复。' })
|
|
736
|
+
return
|
|
737
|
+
}
|
|
738
|
+
setPendingRetire(null)
|
|
739
|
+
setFact(id, 'retired')
|
|
740
|
+
}
|
|
741
|
+
|
|
604
742
|
function setHabitState(text, status, tag) {
|
|
605
743
|
setBusy('habit')
|
|
606
744
|
apiPost('/habit', { text: text, status: status, tag: tag || '' })
|
|
@@ -617,10 +755,31 @@ window.__ModuleLoader__.load({
|
|
|
617
755
|
}
|
|
618
756
|
|
|
619
757
|
if (state === null) {
|
|
758
|
+
// 三态:正在读 / 读失败 / 刚进来还没读。读失败时把 refresh 里 catch 到的真实原因端出来,
|
|
759
|
+
// 别用「重启 DSH 试试」这种猜测盖掉它 —— 重启治不了 403(凭据),也治不了 404(路由)。
|
|
760
|
+
var loadFailed = busy !== 'load' && notice !== null
|
|
620
761
|
return h(
|
|
621
762
|
'div',
|
|
622
763
|
{ className: 'sn-root' },
|
|
623
|
-
h(
|
|
764
|
+
h(
|
|
765
|
+
'div',
|
|
766
|
+
{
|
|
767
|
+
className: 'sn-loading' + (loadFailed ? ' sn-loading-error' : ''),
|
|
768
|
+
role: loadFailed ? 'alert' : 'status',
|
|
769
|
+
},
|
|
770
|
+
loadFailed ? notice.text : '读取中…',
|
|
771
|
+
),
|
|
772
|
+
loadFailed
|
|
773
|
+
? h(
|
|
774
|
+
'div',
|
|
775
|
+
{ className: 'sn-actions' },
|
|
776
|
+
h(
|
|
777
|
+
'button',
|
|
778
|
+
{ type: 'button', className: 'sn-btn', onClick: function () { refresh() }, disabled: busy !== '' },
|
|
779
|
+
'重试',
|
|
780
|
+
),
|
|
781
|
+
)
|
|
782
|
+
: null,
|
|
624
783
|
)
|
|
625
784
|
}
|
|
626
785
|
|
|
@@ -683,7 +842,11 @@ window.__ModuleLoader__.load({
|
|
|
683
842
|
'div',
|
|
684
843
|
{ key: 'actions', className: 'sn-actions' },
|
|
685
844
|
h('button', { className: 'sn-btn', onClick: function () { setFact(fact.id, 'approved') }, disabled: busy !== '' }, '采纳'),
|
|
686
|
-
h(
|
|
845
|
+
h(
|
|
846
|
+
'button',
|
|
847
|
+
{ className: 'sn-btn sn-btn-ghost' + (pendingRetire === fact.id ? ' sn-btn-danger' : ''), onClick: function () { retireFact(fact.id) }, disabled: busy !== '' },
|
|
848
|
+
pendingRetire === fact.id ? '真的退役' : '退役',
|
|
849
|
+
),
|
|
687
850
|
h('button', { className: 'sn-btn sn-btn-ghost', onClick: function () { setFactEdit({ id: fact.id, text: fact.text }) }, disabled: busy !== '' }, '改'),
|
|
688
851
|
)
|
|
689
852
|
: h('div', { key: 'state', className: 'sn-fact-cite' }, '状态:' + statusLabel(fact.status)),
|
|
@@ -794,6 +957,10 @@ window.__ModuleLoader__.load({
|
|
|
794
957
|
return Math.round(hours / 24) + ' 天前'
|
|
795
958
|
}
|
|
796
959
|
|
|
960
|
+
// 没调过 apply 时(或被外部刷新打断)也显示服务端的真值,别显示空白
|
|
961
|
+
var halfLifeShown = halfLife === '' ? String(state.halfLifeDays === undefined ? 30 : state.halfLifeDays) : halfLife
|
|
962
|
+
var profileMaxShown = profileMax === '' ? String((state.profile && state.profile.maxChars) || 110) : profileMax
|
|
963
|
+
|
|
797
964
|
var settingsCard = h(
|
|
798
965
|
'div',
|
|
799
966
|
{ className: 'sn-card', key: 'settings' },
|
|
@@ -829,6 +996,7 @@ window.__ModuleLoader__.load({
|
|
|
829
996
|
h('input', {
|
|
830
997
|
type: 'text',
|
|
831
998
|
maxLength: 24,
|
|
999
|
+
'aria-label': '注入抬头,最多 24 个字,留空就不加抬头',
|
|
832
1000
|
value: heading,
|
|
833
1001
|
placeholder: '例:AI记忆',
|
|
834
1002
|
onChange: function (e) { setHeading(e.target.value) },
|
|
@@ -849,6 +1017,38 @@ window.__ModuleLoader__.load({
|
|
|
849
1017
|
h('button', { className: 'sn-btn', onClick: saveTempTtl, disabled: busy !== '' }, '保存保质期'),
|
|
850
1018
|
),
|
|
851
1019
|
h('div', { className: 'sn-hint' }, '只对「临时」型自述生效(1–90 天,默认 14)。过了期不再自动进上下文,内容一个字都没删 —— 改成「稳定」或重写一条就回来。'),
|
|
1020
|
+
h(
|
|
1021
|
+
'div',
|
|
1022
|
+
{ className: 'sn-limits' },
|
|
1023
|
+
h(
|
|
1024
|
+
'label',
|
|
1025
|
+
{ className: 'sn-field' },
|
|
1026
|
+
h('span', null, '结论半衰期(天)'),
|
|
1027
|
+
h('input', {
|
|
1028
|
+
type: 'number',
|
|
1029
|
+
min: 1,
|
|
1030
|
+
max: 365,
|
|
1031
|
+
'aria-label': '结论半衰期,单位天,范围 1 到 365',
|
|
1032
|
+
value: halfLifeShown,
|
|
1033
|
+
onChange: function (e) { setHalfLife(e.target.value) },
|
|
1034
|
+
}),
|
|
1035
|
+
),
|
|
1036
|
+
h(
|
|
1037
|
+
'label',
|
|
1038
|
+
{ className: 'sn-field' },
|
|
1039
|
+
h('span', null, '画像上限(字)'),
|
|
1040
|
+
h('input', {
|
|
1041
|
+
type: 'number',
|
|
1042
|
+
min: 20,
|
|
1043
|
+
max: 120,
|
|
1044
|
+
'aria-label': '画像上限,单位字,范围 20 到 120',
|
|
1045
|
+
value: profileMaxShown,
|
|
1046
|
+
onChange: function (e) { setProfileMax(e.target.value) },
|
|
1047
|
+
}),
|
|
1048
|
+
),
|
|
1049
|
+
h('button', { className: 'sn-btn', onClick: saveHalfLife, disabled: busy !== '' }, '保存这两项'),
|
|
1050
|
+
),
|
|
1051
|
+
h('div', { className: 'sn-hint' }, '半衰期决定旧结论多久算「半凉」:越小越偏爱最近的。画像上限是「关于他」那段最多占多少字。'),
|
|
852
1052
|
h(
|
|
853
1053
|
'div',
|
|
854
1054
|
{ className: 'sn-field sn-field-wide' },
|
|
@@ -856,6 +1056,7 @@ window.__ModuleLoader__.load({
|
|
|
856
1056
|
h('textarea', {
|
|
857
1057
|
rows: 2,
|
|
858
1058
|
maxLength: 80,
|
|
1059
|
+
'aria-label': '什么时候该写自述,留空就用默认判据',
|
|
859
1060
|
value: noteGuide,
|
|
860
1061
|
placeholder: '例:每次改完一个插件、跑完验收之后写一条;闲聊不用写。',
|
|
861
1062
|
onChange: function (e) { setNoteGuide(e.target.value) },
|
|
@@ -923,6 +1124,7 @@ window.__ModuleLoader__.load({
|
|
|
923
1124
|
'div',
|
|
924
1125
|
{ className: 'sn-kv' },
|
|
925
1126
|
kv('索引状态', h('span', { className: 'sn-kv-tag ' + idxTone }, idxStatus)),
|
|
1127
|
+
kv('检索开关', h('span', { className: 'sn-kv-tag ' + (idx.enabled === true ? 'sn-kv-tag-ok' : 'sn-kv-tag-missing') }, idx.enabled === true ? '已启用' : '已关闭')),
|
|
926
1128
|
kv('已索引会话', String(idx.sessions ?? 0)),
|
|
927
1129
|
kv('磁盘会话', String(idx.disk ?? 0)),
|
|
928
1130
|
kv('待同步会话', String(idx.missing ?? 0)),
|
|
@@ -934,6 +1136,15 @@ window.__ModuleLoader__.load({
|
|
|
934
1136
|
'div',
|
|
935
1137
|
{ className: 'sn-actions' },
|
|
936
1138
|
h('button', { className: 'sn-btn', onClick: rebuildIndex, disabled: busy !== '' }, busy === 'index' ? '重建中…' : '重建索引'),
|
|
1139
|
+
h(
|
|
1140
|
+
'button',
|
|
1141
|
+
{
|
|
1142
|
+
className: idx.enabled === true ? 'sn-btn sn-btn-ghost' : 'sn-btn',
|
|
1143
|
+
onClick: function () { toggleIndex(idx.enabled !== true) },
|
|
1144
|
+
disabled: busy !== '' || (idx.enabled !== true && idx.present !== true),
|
|
1145
|
+
},
|
|
1146
|
+
idx.enabled === true ? '关闭索引' : '启用索引',
|
|
1147
|
+
),
|
|
937
1148
|
h('span', { className: 'sn-grow' }),
|
|
938
1149
|
h('span', { className: 'sn-hint' }, idx.present === true && idx.fresh === false ? '过期时不会用索引查,会退回完整扫描' : '重建期间会占住几秒'),
|
|
939
1150
|
),
|
|
@@ -1423,7 +1634,11 @@ window.__ModuleLoader__.load({
|
|
|
1423
1634
|
var pTotalChars = p0Chars + p1Chars + pGap
|
|
1424
1635
|
var pRestChars = Math.max(0, 320 - pTotalChars)
|
|
1425
1636
|
var pL1Max = Math.max(0, 320 - Math.min(p0Chars, 80) - (p0Chars > 0 ? 1 : 0))
|
|
1426
|
-
var
|
|
1637
|
+
var firstPage = (state.corrections && state.corrections.items) || []
|
|
1638
|
+
// 翻过页就把后面的接上;同一 id 只留第一条(翻页期间新证据插进来时会出现重复)
|
|
1639
|
+
var correctionItems = firstPage.concat(moreEvidence).filter(function (item, at, list) {
|
|
1640
|
+
return list.findIndex(function (other) { return other.id === item.id }) === at
|
|
1641
|
+
})
|
|
1427
1642
|
var pickedCount = pickedEvidence.length
|
|
1428
1643
|
|
|
1429
1644
|
var personaCard = h(
|
|
@@ -1438,7 +1653,7 @@ window.__ModuleLoader__.load({
|
|
|
1438
1653
|
{ className: 'sn-hint' },
|
|
1439
1654
|
'L0 ' + p0Chars + ' + L1 ' + p1Chars + (pGap ? ' + 换行 1' : '') + ' = ' + pTotalChars + ' / 320 字(余 ' + pRestChars + ')',
|
|
1440
1655
|
),
|
|
1441
|
-
personaField('l2', 'L2 扩展设定', '外貌、服饰、世界观、兴趣。这一层不进每回合上下文;保存在同一个 persona.md 里,不占注入预算。', personaValuesNow.l2, Math.max(0, 8000 - pTotalChars), 6),
|
|
1656
|
+
personaField('l2', 'L2 扩展设定', '外貌、服饰、世界观、兴趣。这一层不进每回合上下文;保存在同一个 persona.md 里,不占注入预算。', personaValuesNow.l2, (state.persona && state.persona.l2Limit) || Math.max(0, 8000 - pTotalChars), 6),
|
|
1442
1657
|
h(
|
|
1443
1658
|
'div',
|
|
1444
1659
|
{ className: 'sn-actions' },
|
|
@@ -1448,7 +1663,13 @@ window.__ModuleLoader__.load({
|
|
|
1448
1663
|
h('span', { className: 'sn-muted' }, '保存是整份校验:任一段不合格就一个字都不写。'),
|
|
1449
1664
|
),
|
|
1450
1665
|
|
|
1451
|
-
h(
|
|
1666
|
+
h(
|
|
1667
|
+
'div',
|
|
1668
|
+
{ className: 'sn-sub' },
|
|
1669
|
+
'纠偏候选(' + ((state.corrections && state.corrections.total) || correctionItems.length) + ' 条,这些只是证据' +
|
|
1670
|
+
(correctionItems.length < ((state.corrections && state.corrections.total) || 0) ? ',这里列了 ' + correctionItems.length + ' 条' : '') +
|
|
1671
|
+
')',
|
|
1672
|
+
),
|
|
1452
1673
|
h(
|
|
1453
1674
|
'div',
|
|
1454
1675
|
{ className: 'sn-hint' },
|
|
@@ -1482,10 +1703,19 @@ window.__ModuleLoader__.load({
|
|
|
1482
1703
|
'div',
|
|
1483
1704
|
{ className: 'sn-actions' },
|
|
1484
1705
|
h('button', { className: 'sn-btn sn-btn-ghost', onClick: draftRuleFromPicked, disabled: busy !== '' }, '用选中的 ' + pickedCount + ' 条写一条规则'),
|
|
1706
|
+
h(
|
|
1707
|
+
'button',
|
|
1708
|
+
{ className: 'sn-btn sn-btn-ghost', onClick: discardPicked, disabled: busy !== '' || pickedCount === 0 },
|
|
1709
|
+
'丢弃选中的证据',
|
|
1710
|
+
),
|
|
1711
|
+
(moreCursor !== null ? moreCursor !== '' : state.corrections && state.corrections.nextCursor)
|
|
1712
|
+
? h('button', { className: 'sn-btn sn-btn-ghost', onClick: loadMoreEvidence, disabled: busy !== '' }, busy === 'corrections' ? '翻页中…' : '加载更多')
|
|
1713
|
+
: null,
|
|
1485
1714
|
),
|
|
1486
1715
|
h('textarea', {
|
|
1487
1716
|
className: 'sn-input',
|
|
1488
1717
|
rows: 3,
|
|
1718
|
+
'aria-label': '把选中的证据改写成一条规则',
|
|
1489
1719
|
placeholder: '把上面几句改写成一条能照着做的规则,例如「回答时先举例,再给结论」。',
|
|
1490
1720
|
value: ruleDraft,
|
|
1491
1721
|
onChange: function (e) { setRuleDraft(e.target.value) },
|
|
@@ -1598,11 +1828,65 @@ window.__ModuleLoader__.load({
|
|
|
1598
1828
|
editingThis
|
|
1599
1829
|
? null
|
|
1600
1830
|
: h('button', { className: 'sn-btn sn-btn-ghost', onClick: function () { setFactEdit({ id: fact.id, text: fact.text }) }, disabled: busy !== '' }, '改'),
|
|
1601
|
-
h(
|
|
1831
|
+
h(
|
|
1832
|
+
'button',
|
|
1833
|
+
{ className: 'sn-btn sn-btn-ghost' + (pendingRetire === fact.id ? ' sn-btn-danger' : ''), onClick: function () { retireFact(fact.id) }, disabled: busy !== '' },
|
|
1834
|
+
pendingRetire === fact.id ? '真的退役' : '退役',
|
|
1835
|
+
),
|
|
1602
1836
|
),
|
|
1603
1837
|
)
|
|
1604
1838
|
}),
|
|
1605
1839
|
|
|
1840
|
+
retiredFacts.length > 0
|
|
1841
|
+
? h(
|
|
1842
|
+
'div',
|
|
1843
|
+
null,
|
|
1844
|
+
h('div', { className: 'sn-sub' }, '已退役(' + ((state.facts && state.facts.retiredCount) || retiredFacts.length) + ')'),
|
|
1845
|
+
h(
|
|
1846
|
+
'div',
|
|
1847
|
+
{ className: 'sn-hint' },
|
|
1848
|
+
'退役只是不再进注入,条目本身还在文件里。想让它回来,点「恢复」。' +
|
|
1849
|
+
(retiredFacts.length < ((state.facts && state.facts.retiredCount) || 0)
|
|
1850
|
+
? '(这里只列最近 ' + retiredFacts.length + ' 条)'
|
|
1851
|
+
: ''),
|
|
1852
|
+
),
|
|
1853
|
+
h(
|
|
1854
|
+
'button',
|
|
1855
|
+
{
|
|
1856
|
+
type: 'button',
|
|
1857
|
+
className: 'sn-btn sn-btn-ghost',
|
|
1858
|
+
'aria-expanded': showRetired ? 'true' : 'false',
|
|
1859
|
+
onClick: function () { setShowRetired(!showRetired) },
|
|
1860
|
+
},
|
|
1861
|
+
showRetired ? '收起' : '展开这 ' + retiredFacts.length + ' 条',
|
|
1862
|
+
),
|
|
1863
|
+
showRetired
|
|
1864
|
+
? h(
|
|
1865
|
+
'div',
|
|
1866
|
+
{ className: 'sn-retired-list' },
|
|
1867
|
+
retiredFacts.map(function (fact) {
|
|
1868
|
+
return h(
|
|
1869
|
+
'div',
|
|
1870
|
+
{ key: 'r-' + fact.id, className: 'sn-fact' },
|
|
1871
|
+
h('div', { className: 'sn-fact-text' }, fact.text),
|
|
1872
|
+
h(
|
|
1873
|
+
'div',
|
|
1874
|
+
{ className: 'sn-actions' },
|
|
1875
|
+
h('span', { className: 'sn-heat' }, '退役于 ' + (fact.retiredAt || fact.updatedAt || '—')),
|
|
1876
|
+
h('span', { className: 'sn-grow' }),
|
|
1877
|
+
h(
|
|
1878
|
+
'button',
|
|
1879
|
+
{ type: 'button', className: 'sn-btn', onClick: function () { setFact(fact.id, 'approved') }, disabled: busy !== '' },
|
|
1880
|
+
'恢复',
|
|
1881
|
+
),
|
|
1882
|
+
),
|
|
1883
|
+
)
|
|
1884
|
+
}),
|
|
1885
|
+
)
|
|
1886
|
+
: null,
|
|
1887
|
+
)
|
|
1888
|
+
: null,
|
|
1889
|
+
|
|
1606
1890
|
(state.habits || []).length > 0
|
|
1607
1891
|
? h(
|
|
1608
1892
|
'div',
|
|
@@ -1854,7 +2138,13 @@ window.__ModuleLoader__.load({
|
|
|
1854
2138
|
}),
|
|
1855
2139
|
),
|
|
1856
2140
|
|
|
1857
|
-
|
|
2141
|
+
// 提示要贴顶:记忆页有二十多张卡,人在下半屏点「采纳 / 删除」,
|
|
2142
|
+
// 反馈如果留在文档流顶部,就等于没反馈 —— 会重复点。
|
|
2143
|
+
h(
|
|
2144
|
+
'div',
|
|
2145
|
+
{ className: 'sn-notice-wrap', role: 'status', 'aria-live': 'polite' },
|
|
2146
|
+
h(Notice, { tone: notice && notice.tone, text: notice && notice.text }),
|
|
2147
|
+
),
|
|
1858
2148
|
|
|
1859
2149
|
h(
|
|
1860
2150
|
'div',
|
|
@@ -1952,6 +2242,10 @@ window.__ModuleLoader__.load({
|
|
|
1952
2242
|
'.sn-details summary{cursor:pointer;user-select:none}',
|
|
1953
2243
|
'.sn-mono{font-family:ui-monospace,Menlo,Consolas,monospace;word-break:break-all;padding-top:4px}',
|
|
1954
2244
|
'.sn-loading{padding:16px;font-size:12px;color:var(--dsw-alias-text-secondary,#9ca3af)}',
|
|
2245
|
+
'.sn-loading-error{color:#991b1b}',
|
|
2246
|
+
'.sn-notice-wrap{position:sticky;top:0;z-index:2}',
|
|
2247
|
+
'.sn-notice-wrap>.sn-notice{box-shadow:0 1px 3px rgba(0,0,0,.08)}',
|
|
2248
|
+
'.sn-retired-list{display:flex;flex-direction:column;gap:6px;margin-top:6px}',
|
|
1955
2249
|
".sn-fact-meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap}",
|
|
1956
2250
|
".sn-badge{font-size:11px;background:#eef2ff;color:#3730a3;border-radius:999px;padding:1px 8px}",
|
|
1957
2251
|
".sn-fact-conflict{border-color:#fcd34d;background:#fffdf5}",
|
|
@@ -2037,6 +2331,22 @@ window.__ModuleLoader__.load({
|
|
|
2037
2331
|
'.sn-doc-error{background:#fef2f2;color:#b91c1c}',
|
|
2038
2332
|
'.sn-doc-label{font-weight:600;color:var(--dsw-alias-text-primary,#111827)}',
|
|
2039
2333
|
'.sn-doc-detail{flex:1;min-width:200px;color:var(--dsw-alias-text-secondary,#4b5563)}',
|
|
2334
|
+
// —— 可读性与适配(原来是 11px + #9ca3af,在浅底上几乎要贴到屏幕上看)——
|
|
2335
|
+
'.sn-hint{font-size:12px;color:var(--dsw-alias-text-secondary,#6b7280)}',
|
|
2336
|
+
'.sn-hint-warn{font-size:12px}',
|
|
2337
|
+
'.sn-date,.sn-file-meta,.sn-heat,.sn-evidence-meta,.sn-fact-cite,.sn-card-note{font-size:11.5px}',
|
|
2338
|
+
'.sn-muted{color:var(--dsw-alias-text-secondary,#6b7280)}',
|
|
2339
|
+
// 输入框以前写死 110px,抬头那种长文本被压成一条缝;宽度交给容器,只在窄屏才吃满
|
|
2340
|
+
'.sn-field{flex-wrap:wrap}',
|
|
2341
|
+
'.sn-field input,.sn-field textarea{width:auto;min-width:7em;max-width:100%}',
|
|
2342
|
+
'.sn-field input[type=text],.sn-field textarea{min-width:14em;flex:1}',
|
|
2343
|
+
// 行内操作按钮别藏到 hover 里:触屏没有 hover,键盘用户也未必摸到
|
|
2344
|
+
'.sn-row-actions{opacity:.8}',
|
|
2345
|
+
'@media (hover:none){.sn-row-actions{opacity:1}}',
|
|
2346
|
+
'.sn-expired{color:var(--dsw-alias-text-secondary,#6b7280);border-color:var(--dsw-alias-border-subtle,#d1d5db)}',
|
|
2347
|
+
'@media (max-width:520px){.sn-limits,.sn-row,.sn-actions{flex-wrap:wrap}.sn-field{width:100%}.sn-field input,.sn-field textarea{width:100%}.sn-card{padding:12px}}',
|
|
2348
|
+
// 深色底:插件原先把浅色写死了,宿主切深色时这些块会白得刺眼
|
|
2349
|
+
'@media (prefers-color-scheme:dark){.sn-notice-ok{background:#052e21;color:#a7f3d0}.sn-notice-warn{background:#3f1212;color:#fecaca}.sn-notice{background:var(--dsw-alias-bg-secondary,#1f2937)}.sn-badge,.sn-file-count,.sn-tab-on .sn-tab-badge{background:#1e1b4b;color:#c7d2fe}.sn-material,.sn-kv-tag-stale,.sn-pair-head,.sn-doc-next,.sn-skipped-head{background:#3b2a06;color:#fcd34d}.sn-kv-tag-ok{background:#052e21;color:#6ee7b7}.sn-doc-next{border-color:#92400e}.sn-fact-conflict{background:#2a2308;border-color:#92400e}.sn-doc-ok{background:#052e21;color:#6ee7b7}.sn-doc-error{background:#3f1212;color:#fecaca}.sn-doc-warn{background:#3b2a06;color:#fcd34d}}',
|
|
2040
2350
|
].join('\n')
|
|
2041
2351
|
|
|
2042
2352
|
function apply(ctx) {
|
package/lib/index.js
CHANGED
|
@@ -1115,6 +1115,8 @@ function personaStateOf(inline = false) {
|
|
|
1115
1115
|
// L0 与 L1 之间那个换行也算字数(与注入、校验同一把尺子),
|
|
1116
1116
|
// 否则界面按「320 - L0」给出上限,填满了也存不进去。
|
|
1117
1117
|
l1Limit: Math.max(0, PERSONA_TOTAL - Math.min(l0Chars, PERSONA_L0_MAX) - (l0Chars > 0 ? 1 : 0)),
|
|
1118
|
+
// L2 的上限得跟服务端同一个尺子:界面上写 8000 而这里按 chars 算,才不会两边对不上
|
|
1119
|
+
l2Limit: Math.max(0, MAX_PERSONA_CHARS - (l0Chars + l1Chars + l2Chars)),
|
|
1118
1120
|
fileLimit: MAX_PERSONA_CHARS,
|
|
1119
1121
|
sectionless: persona.sectionless === true,
|
|
1120
1122
|
truncated: l0Chars > PERSONA_L0_MAX,
|
|
@@ -1128,15 +1130,17 @@ function correctionsStateOf() {
|
|
|
1128
1130
|
try {
|
|
1129
1131
|
const events = readEvents()
|
|
1130
1132
|
const watermark = readWatermark()
|
|
1133
|
+
const page = listCorrections({ limit: 30 })
|
|
1131
1134
|
return {
|
|
1132
1135
|
total: events.length,
|
|
1133
1136
|
unused: events.filter((entry) => !entry.used).length,
|
|
1134
1137
|
lastScannedAt: String(watermark?.lastScannedAt ?? ''),
|
|
1135
1138
|
// 界面靠这个列表渲染勾选区;只给计数的话「证据 → 规则」那条链路整段不可用
|
|
1136
|
-
items:
|
|
1139
|
+
items: page.items,
|
|
1140
|
+
nextCursor: page.nextCursor,
|
|
1137
1141
|
}
|
|
1138
1142
|
} catch {
|
|
1139
|
-
return { total: 0, unused: 0, lastScannedAt: '', items: [] }
|
|
1143
|
+
return { total: 0, unused: 0, lastScannedAt: '', items: [], nextCursor: null }
|
|
1140
1144
|
}
|
|
1141
1145
|
}
|
|
1142
1146
|
|
|
@@ -1746,6 +1750,7 @@ function stateOfInner(inline) {
|
|
|
1746
1750
|
// 每行带上「到期没有」:设置页要能把过期的临时自述标出来,而不是让人以为它这回合还在
|
|
1747
1751
|
notes: readNotes().map((entry) => ({ ...entry, expired: isExpiredNote(entry, cfg.tempTtlDays) })),
|
|
1748
1752
|
tempTtlDays: cfg.tempTtlDays,
|
|
1753
|
+
halfLifeDays: cfg.halfLifeDays,
|
|
1749
1754
|
noteGuide: cfg.noteGuide,
|
|
1750
1755
|
notesLastWrite: s.lastWrite,
|
|
1751
1756
|
notesFile: s.notesFile,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-selfnote",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Self-narrative memory for DeepSeek Harness: the model writes notes to itself, which persist across sessions and are injected back. | 人格库:模型写给自己看的话,跨会话保留并每回合注入。",
|
|
6
6
|
"keywords": [
|