dsh-selfnote 0.13.2 → 0.14.0
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 +129 -32
- package/lib/capture-run.js +17 -1
- package/lib/client.js +372 -10
- package/lib/corrections.js +353 -0
- package/lib/index-store.js +14 -5
- package/lib/index.js +751 -65
- package/lib/persona-tx.js +308 -0
- package/lib/persona.js +248 -0
- package/lib/store.js +132 -14
- package/lib/transfer.js +40 -2
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -155,6 +155,29 @@ window.__ModuleLoader__.load({
|
|
|
155
155
|
var headingTuple = useState('')
|
|
156
156
|
var heading = headingTuple[0]
|
|
157
157
|
var setHeading = headingTuple[1]
|
|
158
|
+
// 新写的那条算哪一型:核心先装、稳定默认、临时只活当下
|
|
159
|
+
// 放在最后:上面那些 useState 的调用序号被测试依赖,别插在中间
|
|
160
|
+
var draftTierTuple = useState('stable')
|
|
161
|
+
var draftTier = draftTierTuple[0]
|
|
162
|
+
var setDraftTier = draftTierTuple[1]
|
|
163
|
+
// 临时自述的保质期(天)。只对 temp 型生效,核心与稳定永不过期
|
|
164
|
+
// 同样放最后:新增 useState 只能往后加,别插在中间
|
|
165
|
+
var tempTtlTuple = useState('')
|
|
166
|
+
var tempTtl = tempTtlTuple[0]
|
|
167
|
+
var setTempTtl = tempTtlTuple[1]
|
|
168
|
+
// 人格三段的草稿:null = 直接显示磁盘上的,动过之后才是草稿。
|
|
169
|
+
// 依旧放最后:新增 useState 只能往后加,插在中间会让测试的调用序号错位
|
|
170
|
+
var personaDraftTuple = useState(null)
|
|
171
|
+
var personaDraft = personaDraftTuple[0]
|
|
172
|
+
var setPersonaDraft = personaDraftTuple[1]
|
|
173
|
+
// 纠偏候选里勾了哪几条:只存 id,不存整条(刷新后按 id 找得到才算数)
|
|
174
|
+
var pickedEvidenceTuple = useState([])
|
|
175
|
+
var pickedEvidence = pickedEvidenceTuple[0]
|
|
176
|
+
var setPickedEvidence = pickedEvidenceTuple[1]
|
|
177
|
+
// 用选中的证据写规则时的那段草稿:预填证据原文,要人改写成规则
|
|
178
|
+
var ruleDraftTuple = useState('')
|
|
179
|
+
var ruleDraft = ruleDraftTuple[0]
|
|
180
|
+
var setRuleDraft = ruleDraftTuple[1]
|
|
158
181
|
|
|
159
182
|
function apply(data) {
|
|
160
183
|
setState(data)
|
|
@@ -165,6 +188,7 @@ window.__ModuleLoader__.load({
|
|
|
165
188
|
setMaxEntries(String(data.maxEntries))
|
|
166
189
|
setNoteGuide(typeof data.noteGuide === 'string' ? data.noteGuide : '')
|
|
167
190
|
setHeading(typeof data.heading === 'string' ? data.heading : '')
|
|
191
|
+
setTempTtl(String(data.tempTtlDays === undefined ? 14 : data.tempTtlDays))
|
|
168
192
|
// 每次拿到新状态,列表下标都可能整体挪位:正在改/正等确认的那条已经没意义了
|
|
169
193
|
setEditing(null)
|
|
170
194
|
setPendingDelete(null)
|
|
@@ -277,6 +301,139 @@ window.__ModuleLoader__.load({
|
|
|
277
301
|
})
|
|
278
302
|
}
|
|
279
303
|
|
|
304
|
+
function saveTempTtl() {
|
|
305
|
+
var value = Number(tempTtl)
|
|
306
|
+
if (!Number.isFinite(value) || value < 1 || value > 90) {
|
|
307
|
+
setNotice({ tone: 'warn', text: '保质期要在 1–90 天之间。' })
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
setBusy('ttl')
|
|
311
|
+
apiPost('/config', { tempTtlDays: value })
|
|
312
|
+
.then(function (data) {
|
|
313
|
+
apply(data)
|
|
314
|
+
setNotice({ tone: 'ok', text: '保质期改成 ' + Math.round(value) + ' 天,下一回合生效。' })
|
|
315
|
+
})
|
|
316
|
+
.catch(function (error) {
|
|
317
|
+
setNotice({ tone: 'warn', text: '保存失败:' + error.message })
|
|
318
|
+
})
|
|
319
|
+
.then(function () {
|
|
320
|
+
setBusy('')
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ---- 人格三段 ----
|
|
325
|
+
// 字数按 Unicode 码点算,和服务端、注入预算同一把尺子。
|
|
326
|
+
// 注意:视觉上的一个 emoji 可能由多个码点组成(ZWJ 序列),这里如实算多个。
|
|
327
|
+
function pinCount(text) {
|
|
328
|
+
return Array.from(String(text === undefined || text === null ? '' : text)).length
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function personaValues() { var persona = state.persona || {}
|
|
332
|
+
if (personaDraft === null) {
|
|
333
|
+
return { l0: persona.l0 || '', l1: persona.l1 || '', l2: persona.l2 || '' }
|
|
334
|
+
}
|
|
335
|
+
return personaDraft
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function editPersona(key, value) {
|
|
339
|
+
var next = { l0: personaValues().l0, l1: personaValues().l1, l2: personaValues().l2 }
|
|
340
|
+
next[key] = value
|
|
341
|
+
setPersonaDraft(next)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function sectionlessHint(info) {
|
|
345
|
+
if (!info) return ''
|
|
346
|
+
if (info.error === 'missing') return 'persona.md 还没建,保存一次就有了。'
|
|
347
|
+
if (info.error === 'not-text') return 'persona.md 不像文本(或超过 1MB),先看一眼再改。'
|
|
348
|
+
if (info.sectionless) return '整篇没写标题,暂时全部当作 L1。'
|
|
349
|
+
return 'persona.md · 上次取用 ' + (info.lastReadAt ? String(info.lastReadAt).slice(0, 10) : '从没取过')
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function personaField(key, label, hint, value, max, rows) {
|
|
353
|
+
var used = pinCount(value)
|
|
354
|
+
var over = max > 0 && used > max
|
|
355
|
+
return h(
|
|
356
|
+
'div',
|
|
357
|
+
{ className: 'sn-field sn-field-wide', key: key },
|
|
358
|
+
h(
|
|
359
|
+
'span',
|
|
360
|
+
{ className: 'sn-field-label' },
|
|
361
|
+
label,
|
|
362
|
+
h('span', { className: 'sn-count' + (over ? ' sn-count-over' : '') }, ' ' + used + ' / ' + max + ' 字'),
|
|
363
|
+
),
|
|
364
|
+
h('textarea', {
|
|
365
|
+
className: 'sn-input',
|
|
366
|
+
rows: rows,
|
|
367
|
+
// 上限就是校验线的软提示;真拦住的是保存时那一次整份校验
|
|
368
|
+
maxLength: max > 0 ? max : undefined,
|
|
369
|
+
value: value,
|
|
370
|
+
'data-field': key,
|
|
371
|
+
onChange: function (e) { editPersona(key, e.target.value) },
|
|
372
|
+
}),
|
|
373
|
+
h('div', { className: 'sn-hint' }, hint),
|
|
374
|
+
)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function savePersona() {
|
|
378
|
+
var values = personaValues()
|
|
379
|
+
setBusy('persona')
|
|
380
|
+
apiPost('/persona', {
|
|
381
|
+
l0: values.l0,
|
|
382
|
+
l1: values.l1,
|
|
383
|
+
l2: values.l2,
|
|
384
|
+
consume: pickedEvidence,
|
|
385
|
+
})
|
|
386
|
+
.then(function (data) {
|
|
387
|
+
apply(data)
|
|
388
|
+
setPersonaDraft(null)
|
|
389
|
+
setPickedEvidence([])
|
|
390
|
+
setRuleDraft('')
|
|
391
|
+
if (data.pending === true) {
|
|
392
|
+
setNotice({ tone: 'warn', text: '人格已保存,但证据记账没写完:' + (data.error || '') + '(下次启动会自动补)' })
|
|
393
|
+
} else {
|
|
394
|
+
setNotice({ tone: 'ok', text: '人格已保存,下一回合生效。' })
|
|
395
|
+
}
|
|
396
|
+
})
|
|
397
|
+
.catch(function (error) {
|
|
398
|
+
setNotice({ tone: 'warn', text: '保存失败:' + error.message })
|
|
399
|
+
})
|
|
400
|
+
.then(function () {
|
|
401
|
+
setBusy('')
|
|
402
|
+
})
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function importPersona() {
|
|
406
|
+
setBusy('import')
|
|
407
|
+
apiPost('/persona/import', {})
|
|
408
|
+
.then(function (data) {
|
|
409
|
+
// 导入≠采纳:只把结果填进三个框,人自己看、自己改、自己点保存
|
|
410
|
+
setPersonaDraft({ l0: data.l0 || '', l1: data.l1 || '', l2: data.l2 || '' })
|
|
411
|
+
if (!data.exists) setNotice({ tone: 'warn', text: '没找到可导入的 AGENT.md。' })
|
|
412
|
+
else setNotice({ tone: 'ok', text: '已从 AGENT.md 填进来(还没保存,改完再点「保存人格」)。' })
|
|
413
|
+
})
|
|
414
|
+
.catch(function (error) {
|
|
415
|
+
setNotice({ tone: 'warn', text: '导入失败:' + error.message })
|
|
416
|
+
})
|
|
417
|
+
.then(function () {
|
|
418
|
+
setBusy('')
|
|
419
|
+
})
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function toggleEvidence(id) {
|
|
423
|
+
var next = pickedEvidence.slice()
|
|
424
|
+
var at = next.indexOf(id)
|
|
425
|
+
if (at >= 0) next.splice(at, 1)
|
|
426
|
+
else next.push(id)
|
|
427
|
+
setPickedEvidence(next)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function draftRuleFromPicked() {
|
|
431
|
+
var picked = (correctionItems || []).filter(function (item) { return pickedEvidence.indexOf(item.id) >= 0 })
|
|
432
|
+
var lines = picked.map(function (item) { return '· ' + item.quote })
|
|
433
|
+
setRuleDraft(lines.length > 0 ? lines.join('\n') : '')
|
|
434
|
+
setNotice({ tone: 'ok', text: '把上面几句改写成一条规则,再点「保存人格」。' })
|
|
435
|
+
}
|
|
436
|
+
|
|
280
437
|
function toggleEnabled() {
|
|
281
438
|
setBusy('toggle')
|
|
282
439
|
apiPost('/config', { enabled: !state.enabled })
|
|
@@ -299,11 +456,12 @@ window.__ModuleLoader__.load({
|
|
|
299
456
|
return
|
|
300
457
|
}
|
|
301
458
|
setBusy('note')
|
|
302
|
-
apiPost('/note', { text: text })
|
|
459
|
+
apiPost('/note', { text: text, tier: draftTier })
|
|
303
460
|
.then(function (data) {
|
|
304
461
|
apply(data)
|
|
305
462
|
setDraft('')
|
|
306
|
-
|
|
463
|
+
var add = data.noteAdd || {}
|
|
464
|
+
setNotice(add.warning ? { tone: 'warn', text: '写好了,但' + add.warning } : { tone: 'ok', text: '写好了。' })
|
|
307
465
|
})
|
|
308
466
|
.catch(function (error) {
|
|
309
467
|
setNotice({ tone: 'warn', text: '写入失败:' + error.message })
|
|
@@ -351,6 +509,22 @@ window.__ModuleLoader__.load({
|
|
|
351
509
|
})
|
|
352
510
|
}
|
|
353
511
|
|
|
512
|
+
// 改类型 / pin:正文一个字不动,只换这条排在哪一队
|
|
513
|
+
function setNoteTier(index, patch) {
|
|
514
|
+
setBusy('note-meta')
|
|
515
|
+
apiPost('/note/meta', { index: index, tier: patch.tier, pin: patch.pin })
|
|
516
|
+
.then(function (data) {
|
|
517
|
+
apply(data)
|
|
518
|
+
setNotice({ tone: 'ok', text: '改好了,下一回合按新的位置装。' })
|
|
519
|
+
})
|
|
520
|
+
.catch(function (error) {
|
|
521
|
+
setNotice({ tone: 'warn', text: '没改成:' + error.message })
|
|
522
|
+
})
|
|
523
|
+
.then(function () {
|
|
524
|
+
setBusy('')
|
|
525
|
+
})
|
|
526
|
+
}
|
|
527
|
+
|
|
354
528
|
function removeNote(index) {
|
|
355
529
|
if (pendingDelete !== index) {
|
|
356
530
|
setPendingDelete(index)
|
|
@@ -521,6 +695,7 @@ window.__ModuleLoader__.load({
|
|
|
521
695
|
var tabs = [
|
|
522
696
|
{ key: 'overview', label: '概览' },
|
|
523
697
|
{ key: 'notes', label: '自述', badge: notes.length + ' 条' },
|
|
698
|
+
{ key: 'persona', label: '人格', badge: ((state.persona && state.persona.chars) || 0) + ' 字' },
|
|
524
699
|
{ key: 'memory', label: '记忆', badge: candidates.length > 0 ? '待审 ' + candidates.length : approvedFacts.length + ' 条' },
|
|
525
700
|
{ key: 'profile', label: '画像', badge: (state.profile ? state.profile.chars : 0) + ' 字' },
|
|
526
701
|
{ key: 'injection', label: '注入', badge: ((state.injection && state.injection.chars) || 0) + ' 字' },
|
|
@@ -545,9 +720,10 @@ window.__ModuleLoader__.load({
|
|
|
545
720
|
}
|
|
546
721
|
|
|
547
722
|
var budget = state.budget || {}
|
|
548
|
-
var limit = budget.limit ||
|
|
549
|
-
var caps = budget.caps || { notes: 200, profile:
|
|
723
|
+
var limit = budget.limit || 1000
|
|
724
|
+
var caps = budget.caps || { persona: 320, notes: 200, profile: 120, env: 230, facts: 90, habits: 40 }
|
|
550
725
|
var segments = [
|
|
726
|
+
{ key: 'persona', label: '人格', value: budget.persona || 0, color: '#f97316' },
|
|
551
727
|
{ key: 'notes', label: '自述', value: budget.notes || 0, color: '#60a5fa' },
|
|
552
728
|
{ key: 'profile', label: '画像', value: budget.profile || 0, color: '#a78bfa' },
|
|
553
729
|
// 环境段是最大的一块(上限 320 字),漏了它进度条就画不满:
|
|
@@ -654,7 +830,7 @@ window.__ModuleLoader__.load({
|
|
|
654
830
|
type: 'text',
|
|
655
831
|
maxLength: 24,
|
|
656
832
|
value: heading,
|
|
657
|
-
placeholder: '
|
|
833
|
+
placeholder: '例:AI记忆',
|
|
658
834
|
onChange: function (e) { setHeading(e.target.value) },
|
|
659
835
|
}),
|
|
660
836
|
),
|
|
@@ -666,6 +842,13 @@ window.__ModuleLoader__.load({
|
|
|
666
842
|
h('button', { className: 'sn-btn sn-btn-ghost', onClick: saveHeading, disabled: busy !== '' }, '保存抬头'),
|
|
667
843
|
),
|
|
668
844
|
h('div', { className: 'sn-hint' }, '上次写入:' + lastWriteText(state.notesLastWrite)),
|
|
845
|
+
h(
|
|
846
|
+
'div',
|
|
847
|
+
{ className: 'sn-limits' },
|
|
848
|
+
h('label', { className: 'sn-field' }, h('span', null, '临时自述保质期(天)'), h('input', { type: 'number', min: 1, max: 90, value: tempTtl, onChange: function (e) { setTempTtl(e.target.value) } })),
|
|
849
|
+
h('button', { className: 'sn-btn', onClick: saveTempTtl, disabled: busy !== '' }, '保存保质期'),
|
|
850
|
+
),
|
|
851
|
+
h('div', { className: 'sn-hint' }, '只对「临时」型自述生效(1–90 天,默认 14)。过了期不再自动进上下文,内容一个字都没删 —— 改成「稳定」或重写一条就回来。'),
|
|
669
852
|
h(
|
|
670
853
|
'div',
|
|
671
854
|
{ className: 'sn-field sn-field-wide' },
|
|
@@ -1049,12 +1232,31 @@ window.__ModuleLoader__.load({
|
|
|
1049
1232
|
h(
|
|
1050
1233
|
'div',
|
|
1051
1234
|
{ className: 'sn-actions' },
|
|
1235
|
+
h(
|
|
1236
|
+
'select',
|
|
1237
|
+
{
|
|
1238
|
+
className: 'sn-tier',
|
|
1239
|
+
value: draftTier,
|
|
1240
|
+
title: '核心=身份素材(不进每回合注入,只作提炼人格的原料);稳定=表达习惯与工作偏好(先装,★ 优先);临时=当前阶段在做什么',
|
|
1241
|
+
onChange: function (e) { setDraftTier(e.target.value) },
|
|
1242
|
+
},
|
|
1243
|
+
h('option', { value: 'core' }, '核心身份'),
|
|
1244
|
+
h('option', { value: 'stable' }, '稳定偏好'),
|
|
1245
|
+
h('option', { value: 'temp' }, '临时自述'),
|
|
1246
|
+
),
|
|
1052
1247
|
h('button', { className: 'sn-btn', onClick: addNote, disabled: busy !== '' }, '写进去'),
|
|
1053
1248
|
h('span', { className: 'sn-grow' }),
|
|
1054
1249
|
h('button', { className: 'sn-btn sn-btn-danger', onClick: clearAll, disabled: busy !== '' }, confirming ? '真的清空' : '清空当前(进归档)'),
|
|
1055
1250
|
),
|
|
1056
1251
|
)
|
|
1057
1252
|
|
|
1253
|
+
var tierCounts = { core: 0, stable: 0, temp: 0, expired: 0 }
|
|
1254
|
+
notes.forEach(function (entry) {
|
|
1255
|
+
var key = entry.tier === 'core' || entry.tier === 'temp' ? entry.tier : 'stable'
|
|
1256
|
+
tierCounts[key] += 1
|
|
1257
|
+
if (entry.expired === true) tierCounts.expired += 1
|
|
1258
|
+
})
|
|
1259
|
+
|
|
1058
1260
|
var notesList = h(
|
|
1059
1261
|
'div',
|
|
1060
1262
|
{ className: 'sn-card', key: 'notes' },
|
|
@@ -1062,7 +1264,13 @@ window.__ModuleLoader__.load({
|
|
|
1062
1264
|
h(
|
|
1063
1265
|
'div',
|
|
1064
1266
|
{ className: 'sn-hint' },
|
|
1065
|
-
'
|
|
1267
|
+
'只装「稳定(★ 钉住的先装)」和「临时」,合计 ≤ ' + caps.notes + ' 字。放不下的整条跳过、不截半句,注入里会留一句「另有 N 条」当门牌。改完/删完下一回合生效。',
|
|
1268
|
+
),
|
|
1269
|
+
h(
|
|
1270
|
+
'div',
|
|
1271
|
+
{ className: 'sn-hint' },
|
|
1272
|
+
'核心 ' + tierCounts.core + ' 条 稳定 ' + tierCounts.stable + ' 条 临时 ' + tierCounts.temp + ' 条' +
|
|
1273
|
+
(tierCounts.expired > 0 ? '(其中 ' + tierCounts.expired + ' 条已过期,不再自动进场)' : ''),
|
|
1066
1274
|
),
|
|
1067
1275
|
reversed.length === 0
|
|
1068
1276
|
? h('div', { className: 'sn-empty' }, '还没有任何自述。模型攒到第一条时会出现在这里。')
|
|
@@ -1096,12 +1304,53 @@ window.__ModuleLoader__.load({
|
|
|
1096
1304
|
h('button', { className: 'sn-btn sn-btn-ghost', onClick: cancelEditNote, disabled: busy !== '' }, '取消'),
|
|
1097
1305
|
),
|
|
1098
1306
|
)
|
|
1099
|
-
: h(
|
|
1307
|
+
: h(
|
|
1308
|
+
'span',
|
|
1309
|
+
{ className: 'sn-text' },
|
|
1310
|
+
entry.text,
|
|
1311
|
+
entry.tier === 'core'
|
|
1312
|
+
? h(
|
|
1313
|
+
'span',
|
|
1314
|
+
{ className: 'sn-material', title: '核心身份现在是提炼人格的素材:它不再进每回合注入。要去「人格」页把要点写进 L0 / L1 才生效' },
|
|
1315
|
+
'素材(不注入)',
|
|
1316
|
+
)
|
|
1317
|
+
: null,
|
|
1318
|
+
entry.expired === true
|
|
1319
|
+
? h(
|
|
1320
|
+
'span',
|
|
1321
|
+
{ className: 'sn-expired', title: '临时自述过了保质期,不再自动进上下文;内容还在,改成「稳定」或重写一条就回来' },
|
|
1322
|
+
'已过期',
|
|
1323
|
+
)
|
|
1324
|
+
: null,
|
|
1325
|
+
),
|
|
1100
1326
|
isEditing
|
|
1101
1327
|
? null
|
|
1102
1328
|
: h(
|
|
1103
1329
|
'span',
|
|
1104
1330
|
{ className: 'sn-row-actions' },
|
|
1331
|
+
h(
|
|
1332
|
+
'select',
|
|
1333
|
+
{
|
|
1334
|
+
className: 'sn-tier',
|
|
1335
|
+
value: entry.tier || 'stable',
|
|
1336
|
+
title: '这条属于哪一型:核心先装(人格、长期身份)、稳定按 pin 排、临时最后',
|
|
1337
|
+
onChange: function (e) { setNoteTier(stored, { tier: e.target.value }) },
|
|
1338
|
+
disabled: busy !== '',
|
|
1339
|
+
},
|
|
1340
|
+
h('option', { value: 'core' }, '核心'),
|
|
1341
|
+
h('option', { value: 'stable' }, '稳定'),
|
|
1342
|
+
h('option', { value: 'temp' }, '临时'),
|
|
1343
|
+
),
|
|
1344
|
+
h(
|
|
1345
|
+
'button',
|
|
1346
|
+
{
|
|
1347
|
+
className: 'sn-btn sn-btn-ghost sn-pin' + (entry.pin === true ? ' sn-pin-on' : ''),
|
|
1348
|
+
title: entry.pin === true ? '已钉住:同一型里优先装进去(再点取消)' : '钉住:同一型里优先装进去',
|
|
1349
|
+
onClick: function () { setNoteTier(stored, { pin: entry.pin !== true }) },
|
|
1350
|
+
disabled: busy !== '',
|
|
1351
|
+
},
|
|
1352
|
+
entry.pin === true ? '★' : '☆',
|
|
1353
|
+
),
|
|
1105
1354
|
h('button', { className: 'sn-btn sn-btn-ghost', onClick: function () { startEditNote(stored, entry.text) }, disabled: busy !== '' }, '改'),
|
|
1106
1355
|
h('button', { className: 'sn-btn sn-btn-danger', onClick: function () { removeNote(stored) }, disabled: busy !== '' }, pendingDelete === stored ? '真的删' : '删'),
|
|
1107
1356
|
),
|
|
@@ -1164,6 +1413,106 @@ window.__ModuleLoader__.load({
|
|
|
1164
1413
|
: null,
|
|
1165
1414
|
)
|
|
1166
1415
|
|
|
1416
|
+
var persona = state.persona || {}
|
|
1417
|
+
var personaValuesNow = personaValues()
|
|
1418
|
+
var p0Chars = pinCount(personaValuesNow.l0)
|
|
1419
|
+
var p1Chars = pinCount(personaValuesNow.l1)
|
|
1420
|
+
// L0 与 L1 之间那一个换行也占 320 里的字数(与保存校验同一把尺子),
|
|
1421
|
+
// 否则界面会绿着「320 / 320」,点保存却被拒。
|
|
1422
|
+
var pGap = p0Chars > 0 && p1Chars > 0 ? 1 : 0
|
|
1423
|
+
var pTotalChars = p0Chars + p1Chars + pGap
|
|
1424
|
+
var pRestChars = Math.max(0, 320 - pTotalChars)
|
|
1425
|
+
var pL1Max = Math.max(0, 320 - Math.min(p0Chars, 80) - (p0Chars > 0 ? 1 : 0))
|
|
1426
|
+
var correctionItems = (state.corrections && state.corrections.items) || []
|
|
1427
|
+
var pickedCount = pickedEvidence.length
|
|
1428
|
+
|
|
1429
|
+
var personaCard = h(
|
|
1430
|
+
'div',
|
|
1431
|
+
{ className: 'sn-card', key: 'persona' },
|
|
1432
|
+
h('div', { className: 'sn-card-title' }, '人格三段(L0 / L1 每回合进,L2 按需取)'),
|
|
1433
|
+
h('div', { className: 'sn-hint' }, '这里只放「要是没人提醒,我就会忘」的那些。L0 和 L1 每回合都进上下文;L2 不进,只在 self_note 去取的时候出现。'),
|
|
1434
|
+
personaField('l0', 'L0 核心身份', '我是谁、和谁说话、怎么称呼。最先装;超过 80 字会被截断(并在这里标出来)。', personaValuesNow.l0, 80, 2),
|
|
1435
|
+
personaField('l1', 'L1 行为规则', '语气、回答骨架、情境优先级、禁忌边界。一行一条;装不下的整行跳过,不截半句。', personaValuesNow.l1, pL1Max, 6),
|
|
1436
|
+
h(
|
|
1437
|
+
'div',
|
|
1438
|
+
{ className: 'sn-hint' },
|
|
1439
|
+
'L0 ' + p0Chars + ' + L1 ' + p1Chars + (pGap ? ' + 换行 1' : '') + ' = ' + pTotalChars + ' / 320 字(余 ' + pRestChars + ')',
|
|
1440
|
+
),
|
|
1441
|
+
personaField('l2', 'L2 扩展设定', '外貌、服饰、世界观、兴趣。这一层不进每回合上下文;保存在同一个 persona.md 里,不占注入预算。', personaValuesNow.l2, Math.max(0, 8000 - pTotalChars), 6),
|
|
1442
|
+
h(
|
|
1443
|
+
'div',
|
|
1444
|
+
{ className: 'sn-actions' },
|
|
1445
|
+
h('button', { className: 'sn-btn', onClick: savePersona, disabled: busy !== '' }, '保存人格'),
|
|
1446
|
+
h('button', { className: 'sn-btn sn-btn-ghost', onClick: importPersona, disabled: busy !== '' }, '从 AGENT.md 导入'),
|
|
1447
|
+
h('span', { className: 'sn-grow' }),
|
|
1448
|
+
h('span', { className: 'sn-muted' }, '保存是整份校验:任一段不合格就一个字都不写。'),
|
|
1449
|
+
),
|
|
1450
|
+
|
|
1451
|
+
h('div', { className: 'sn-sub' }, '纠偏候选(' + correctionItems.length + ' 条,这些只是证据)'),
|
|
1452
|
+
h(
|
|
1453
|
+
'div',
|
|
1454
|
+
{ className: 'sn-hint' },
|
|
1455
|
+
'模型能读到它们,但你没有写入权限 —— 采纳必须由人在这儿点。勾几条 → 改写成一条规则 → 保存人格时才算用过这些证据。',
|
|
1456
|
+
),
|
|
1457
|
+
correctionItems.length === 0
|
|
1458
|
+
? h('div', { className: 'sn-empty' }, '还没攒到纠偏证据。你纠正我之后,这里会慢慢出现原话。')
|
|
1459
|
+
: h(
|
|
1460
|
+
'ul',
|
|
1461
|
+
{ className: 'sn-list' },
|
|
1462
|
+
correctionItems.map(function (item) {
|
|
1463
|
+
var picked = pickedEvidence.indexOf(item.id) >= 0
|
|
1464
|
+
return h(
|
|
1465
|
+
'li',
|
|
1466
|
+
{ key: item.id, className: 'sn-item' },
|
|
1467
|
+
h(
|
|
1468
|
+
'label',
|
|
1469
|
+
{ className: 'sn-toggle sn-evidence' },
|
|
1470
|
+
h('input', {
|
|
1471
|
+
type: 'checkbox',
|
|
1472
|
+
checked: picked,
|
|
1473
|
+
onChange: function () { toggleEvidence(item.id) },
|
|
1474
|
+
}),
|
|
1475
|
+
h('span', { className: 'sn-evidence-quote' }, item.quote),
|
|
1476
|
+
),
|
|
1477
|
+
h('span', { className: 'sn-evidence-meta' }, (item.at || '').slice(0, 10) + ' · ' + (item.session || '')),
|
|
1478
|
+
)
|
|
1479
|
+
}),
|
|
1480
|
+
),
|
|
1481
|
+
h(
|
|
1482
|
+
'div',
|
|
1483
|
+
{ className: 'sn-actions' },
|
|
1484
|
+
h('button', { className: 'sn-btn sn-btn-ghost', onClick: draftRuleFromPicked, disabled: busy !== '' }, '用选中的 ' + pickedCount + ' 条写一条规则'),
|
|
1485
|
+
),
|
|
1486
|
+
h('textarea', {
|
|
1487
|
+
className: 'sn-input',
|
|
1488
|
+
rows: 3,
|
|
1489
|
+
placeholder: '把上面几句改写成一条能照着做的规则,例如「回答时先举例,再给结论」。',
|
|
1490
|
+
value: ruleDraft,
|
|
1491
|
+
onChange: function (e) { setRuleDraft(e.target.value) },
|
|
1492
|
+
}),
|
|
1493
|
+
h(
|
|
1494
|
+
'div',
|
|
1495
|
+
{ className: 'sn-actions' },
|
|
1496
|
+
h(
|
|
1497
|
+
'button',
|
|
1498
|
+
{
|
|
1499
|
+
className: 'sn-btn',
|
|
1500
|
+
disabled: busy !== '' || ruleDraft.trim() === '',
|
|
1501
|
+
onClick: function () {
|
|
1502
|
+
var body = ruleDraft.trim()
|
|
1503
|
+
if (body === '') return
|
|
1504
|
+
var nextL1 = (personaValues().l1 + String.fromCharCode(10) + body).trim()
|
|
1505
|
+
editPersona('l1', nextL1)
|
|
1506
|
+
setNotice({ tone: 'ok', text: '规则已加进 L1 草稿,还没保存 —— 确认没问题再点「保存人格」。' })
|
|
1507
|
+
},
|
|
1508
|
+
},
|
|
1509
|
+
'加进 L1(先不进盘)',
|
|
1510
|
+
),
|
|
1511
|
+
h('span', { className: 'sn-grow' }),
|
|
1512
|
+
h('span', { className: 'sn-muted' }, sectionlessHint(persona)),
|
|
1513
|
+
),
|
|
1514
|
+
)
|
|
1515
|
+
|
|
1167
1516
|
var memoryCard = h(
|
|
1168
1517
|
'div',
|
|
1169
1518
|
{ className: 'sn-card', key: 'memory' },
|
|
@@ -1325,8 +1674,8 @@ window.__ModuleLoader__.load({
|
|
|
1325
1674
|
h(
|
|
1326
1675
|
'div',
|
|
1327
1676
|
{ className: 'sn-card-note' },
|
|
1328
|
-
(inj.chars || 0) + ' / ' + (inj.limit ||
|
|
1329
|
-
(inj.overhead > 0 ? '
|
|
1677
|
+
(inj.chars || 0) + ' / ' + (inj.limit || 1000) + ' 字' +
|
|
1678
|
+
(inj.overhead > 0 ? '(固定开销 ' + inj.overhead + ' 字)' : ''),
|
|
1330
1679
|
),
|
|
1331
1680
|
),
|
|
1332
1681
|
h('div', { className: 'sn-hint' }, '下面这段每回合都贴在用户消息前面。它只改变「我」看到的背景,不会出现在你的输入框里。'),
|
|
@@ -1442,6 +1791,7 @@ window.__ModuleLoader__.load({
|
|
|
1442
1791
|
var panels = {
|
|
1443
1792
|
overview: [statusBar, budgetBlock, statsRow, settingsCard, doctorCard, indexCard, filesBlock, transferCard],
|
|
1444
1793
|
notes: [writeCard, notesList],
|
|
1794
|
+
persona: [personaCard],
|
|
1445
1795
|
memory: [memoryCard],
|
|
1446
1796
|
profile: [profileCard],
|
|
1447
1797
|
injection: [injectionCard, attributionCard],
|
|
@@ -1449,6 +1799,7 @@ window.__ModuleLoader__.load({
|
|
|
1449
1799
|
var panelHints = {
|
|
1450
1800
|
overview: '注入开关、容量上限,以及索引与文件位置。',
|
|
1451
1801
|
notes: 'agent 写给自己看的话,攒多了从最旧的往 archive 挪。每一行都能改、能删。',
|
|
1802
|
+
persona: '三段人格:L0/L1 每回合都进上下文,L2 按需取。纠偏证据在这里变成规则,但必须人来拍板。',
|
|
1452
1803
|
memory: '候选、已采纳、可以升成习惯的标签;采纳只能人来点。',
|
|
1453
1804
|
profile: '从已采纳记忆聚合出的「关于你」,一句一出处。',
|
|
1454
1805
|
injection: '这一回合真的会贴进提示词的那段字,逐段拆开看谁进了、谁被挤掉。',
|
|
@@ -1475,7 +1826,7 @@ window.__ModuleLoader__.load({
|
|
|
1475
1826
|
h('li', null, h('b', null, '关于你(画像)'), ' 从「已采纳」的事实里聚合,按标签每组取最新一条,每句都能点回原句;可以手改覆盖。'),
|
|
1476
1827
|
h('li', null, h('b', null, '常驻结论'), ' 已采纳的事实里,画像没吸收到的那些。'),
|
|
1477
1828
|
h('li', null, h('b', null, '习惯'), ' 由人点出来才生效,模型自己升不了。'),
|
|
1478
|
-
h('li', { className: 'sn-intro-note' }, '每回合合计 ≤' + limit + '
|
|
1829
|
+
h('li', { className: 'sn-intro-note' }, '每回合合计 ≤' + limit + ' 字:人格 ' + caps.persona + ' / 自述 ' + caps.notes + ' / 画像 ' + caps.profile + ' / 环境 ' + caps.env + ' / 结论 ' + caps.facts + ' / 习惯 ' + caps.habits + '。放不下的那一条会跳过、不截半句。这几段的上限写死在插件里(不是设置项);检索工具不受这个限制。'),
|
|
1479
1830
|
),
|
|
1480
1831
|
),
|
|
1481
1832
|
|
|
@@ -1575,6 +1926,17 @@ window.__ModuleLoader__.load({
|
|
|
1575
1926
|
'.sn-edit{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px}',
|
|
1576
1927
|
'.sn-row-actions{display:flex;gap:6px;margin-left:auto;flex:none;opacity:.55;transition:opacity .12s ease}',
|
|
1577
1928
|
'.sn-item:hover .sn-row-actions,.sn-item:focus-within .sn-row-actions{opacity:1}',
|
|
1929
|
+
'.sn-tier{font-size:11px;font-family:inherit;padding:2px 4px;border-radius:6px;border:1px solid var(--dsw-alias-border-subtle,#d1d5db);background:var(--dsw-alias-bg-primary,#fff);color:inherit;cursor:pointer}',
|
|
1930
|
+
'.sn-pin{padding:2px 8px;line-height:1.6}',
|
|
1931
|
+
'.sn-pin-on{color:#b45309;border-color:#fcd34d}',
|
|
1932
|
+
'.sn-expired{font-size:11px;color:#9ca3af;border:1px solid #e5e7eb;border-radius:6px;padding:0 6px;margin-left:6px;white-space:nowrap}',
|
|
1933
|
+
'.sn-material{font-size:11px;color:#b45309;border:1px solid #fcd34d;background:#fffbeb;border-radius:6px;padding:0 6px;margin-left:6px;white-space:nowrap}',
|
|
1934
|
+
'.sn-field-label{display:flex;align-items:baseline;gap:4px;font-weight:600}',
|
|
1935
|
+
'.sn-count{font-size:12px;font-weight:400;color:var(--dsw-alias-text-quaternary,#9ca3af);font-variant-numeric:tabular-nums}',
|
|
1936
|
+
'.sn-count-over{color:#dc2626;font-weight:600}',
|
|
1937
|
+
'.sn-evidence{align-items:flex-start;gap:8px}',
|
|
1938
|
+
'.sn-evidence-quote{line-height:1.6}',
|
|
1939
|
+
'.sn-evidence-meta{font-size:11px;color:var(--dsw-alias-text-quaternary,#9ca3af);white-space:nowrap}',
|
|
1578
1940
|
'.sn-fact-edit{display:flex;flex-direction:column;gap:6px}',
|
|
1579
1941
|
'.sn-empty{font-size:12px;color:var(--dsw-alias-text-secondary,#9ca3af)}',
|
|
1580
1942
|
'.sn-sources{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:6px}',
|