dsh-waifu-agents 0.3.1 → 0.3.2
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/client.js +81 -38
- package/package.json +1 -1
- package/panel-ops.js +26 -4
package/client.js
CHANGED
|
@@ -199,7 +199,8 @@ window.__ModuleLoader__.load({
|
|
|
199
199
|
name: '名字',
|
|
200
200
|
description: '简述',
|
|
201
201
|
bodyPlaceholder: '只写她独有的部分;公共条款在骨架里',
|
|
202
|
-
|
|
202
|
+
save: '保存',
|
|
203
|
+
saveHint: '改动要点「保存」才会写入',
|
|
203
204
|
nameDrivesFile: '文件名会跟着「名字」走',
|
|
204
205
|
saving: '保存中…',
|
|
205
206
|
saved: '已保存',
|
|
@@ -533,7 +534,8 @@ window.__ModuleLoader__.load({
|
|
|
533
534
|
name: 'Name',
|
|
534
535
|
description: 'Summary',
|
|
535
536
|
bodyPlaceholder: 'Only her own part; the shared clauses live in the skeleton',
|
|
536
|
-
|
|
537
|
+
save: 'save',
|
|
538
|
+
saveHint: 'Changes are written when you press Save',
|
|
537
539
|
nameDrivesFile: 'The file name follows this name',
|
|
538
540
|
saving: 'Saving…',
|
|
539
541
|
saved: 'Saved',
|
|
@@ -1603,22 +1605,30 @@ window.__ModuleLoader__.load({
|
|
|
1603
1605
|
* 提示词。`Confirm` / `Alert` 那种一句话对话框仍然走平台,两者不冲突:
|
|
1604
1606
|
* 这里只借用同一层遮罩色和同一套主题 token。
|
|
1605
1607
|
*
|
|
1606
|
-
*
|
|
1607
|
-
*
|
|
1608
|
+
* 收尾路径分两档。未锁定时三条都要有:右上角的关闭、点遮罩、Esc——少一
|
|
1609
|
+
* 条,用户就会在某一次编辑后发现"关不掉",而那正是弹窗最容易招人烦的地
|
|
1610
|
+
* 方。锁定(`locked`)时只剩显式按钮:编辑器正攥着一份未保存的草稿,一
|
|
1611
|
+
* 次走神的点击落在遮罩上、或者一个惯性 Esc,就把整段正文无声丢掉——这
|
|
1612
|
+
* 种"没有确认、没有恢复"的丢失比关不掉恶劣得多,所以两个编辑器都锁。
|
|
1608
1613
|
*/
|
|
1609
|
-
function Sheet({ open, title, hint, width, t, onClose, footer, children }) {
|
|
1614
|
+
function Sheet({ open, title, hint, width, t, onClose, footer, children, locked }) {
|
|
1610
1615
|
React.useEffect(() => {
|
|
1611
1616
|
if (open !== true) return undefined;
|
|
1617
|
+
if (locked === true) return undefined;
|
|
1612
1618
|
const onKey = (event) => {
|
|
1613
1619
|
if (event.key === 'Escape') onClose();
|
|
1614
1620
|
};
|
|
1615
1621
|
document.addEventListener('keydown', onKey);
|
|
1616
1622
|
return () => document.removeEventListener('keydown', onKey);
|
|
1617
|
-
}, [open, onClose]);
|
|
1623
|
+
}, [open, onClose, locked]);
|
|
1618
1624
|
if (open !== true) return null;
|
|
1619
1625
|
return h(
|
|
1620
1626
|
'div',
|
|
1621
|
-
{
|
|
1627
|
+
{
|
|
1628
|
+
style: { ...L.overlay, zIndex: 60 },
|
|
1629
|
+
// 锁定时遮罩不再接 onClose:点外面什么也不发生,关窗只能走按钮。
|
|
1630
|
+
onClick: locked === true ? undefined : onClose,
|
|
1631
|
+
},
|
|
1622
1632
|
h(
|
|
1623
1633
|
'div',
|
|
1624
1634
|
{
|
|
@@ -1917,8 +1927,8 @@ window.__ModuleLoader__.load({
|
|
|
1917
1927
|
* a draft with unsaved keystrokes in it, and a preview that reads from the
|
|
1918
1928
|
* draft can never be what the model sees. This one is fetched from the Host
|
|
1919
1929
|
* (`get` again), so it shows the file as it is on disk with the skeleton
|
|
1920
|
-
* poured in —
|
|
1921
|
-
*
|
|
1930
|
+
* poured in — "on disk" now means "the last version you pressed Save on";
|
|
1931
|
+
* edits still in the editor are deliberately not in it.
|
|
1922
1932
|
*/
|
|
1923
1933
|
function PreviewWindow({ open, title, text, loading, error, t, onClose }) {
|
|
1924
1934
|
if (open !== true) return null;
|
|
@@ -1998,7 +2008,20 @@ window.__ModuleLoader__.load({
|
|
|
1998
2008
|
);
|
|
1999
2009
|
}
|
|
2000
2010
|
|
|
2001
|
-
|
|
2011
|
+
/*
|
|
2012
|
+
* 人设编辑器:弹窗里的一张表,改动**不**自动保存。
|
|
2013
|
+
*
|
|
2014
|
+
* 曾经每个输入框失焦就写一次盘("改动会在离开输入框时自动保存")。
|
|
2015
|
+
* 那意味着"保存"这个动作不可见:点谁、路过谁都会触发一次写入,而
|
|
2016
|
+
* 写一半想去看看别人、再回来的路上一句话已经被存成了半句。现在写入
|
|
2017
|
+
* 只有一个入口——下面的「保存」按钮;关掉窗口(× 或「取消」)丢掉
|
|
2018
|
+
* 没保存的改动,文件保持上一次保存的样子。
|
|
2019
|
+
*
|
|
2020
|
+
* `dirty` 由调用方算(`draftKeyOf(draft) !== draft.savedKey`):它让
|
|
2021
|
+
* 「保存」在没有改动时是灰的,替代旧模型里"没改动就不发请求"的
|
|
2022
|
+
* 静默去重——去重还在(`save` 里),只是用户现在也看得见。
|
|
2023
|
+
*/
|
|
2024
|
+
function Editor({ draft, t, busy, onPatch, onSave, onCancel, dirty, tall }) {
|
|
2002
2025
|
const loading = draft.loading === true;
|
|
2003
2026
|
const field = (label, ...controls) => h('div', { style: L.field }, h('span', { style: L.label }, label), ...controls);
|
|
2004
2027
|
const text = draft.body ?? '';
|
|
@@ -2017,7 +2040,6 @@ window.__ModuleLoader__.load({
|
|
|
2017
2040
|
value: draft.name ?? '',
|
|
2018
2041
|
disabled: busy,
|
|
2019
2042
|
onChange: (event) => onPatch({ name: event.target.value }),
|
|
2020
|
-
onBlur: onCommit,
|
|
2021
2043
|
}),
|
|
2022
2044
|
),
|
|
2023
2045
|
field(
|
|
@@ -2027,7 +2049,6 @@ window.__ModuleLoader__.load({
|
|
|
2027
2049
|
value: draft.description ?? '',
|
|
2028
2050
|
disabled: busy,
|
|
2029
2051
|
onChange: (event) => onPatch({ description: event.target.value }),
|
|
2030
|
-
onBlur: onCommit,
|
|
2031
2052
|
}),
|
|
2032
2053
|
),
|
|
2033
2054
|
field(
|
|
@@ -2037,7 +2058,6 @@ window.__ModuleLoader__.load({
|
|
|
2037
2058
|
value: draft.group ?? '',
|
|
2038
2059
|
disabled: busy,
|
|
2039
2060
|
onChange: (event) => onPatch({ group: event.target.value }),
|
|
2040
|
-
onBlur: onCommit,
|
|
2041
2061
|
}),
|
|
2042
2062
|
),
|
|
2043
2063
|
field(
|
|
@@ -2046,10 +2066,7 @@ window.__ModuleLoader__.load({
|
|
|
2046
2066
|
checked: (draft.template ?? '') === SKELETON_FILE,
|
|
2047
2067
|
disabled: busy,
|
|
2048
2068
|
title: t('useSkeleton'),
|
|
2049
|
-
onChange: (next) => {
|
|
2050
|
-
onPatch({ template: next ? SKELETON_FILE : '' });
|
|
2051
|
-
onCommit();
|
|
2052
|
-
},
|
|
2069
|
+
onChange: (next) => onPatch({ template: next ? SKELETON_FILE : '' }),
|
|
2053
2070
|
}),
|
|
2054
2071
|
),
|
|
2055
2072
|
/*
|
|
@@ -2064,7 +2081,6 @@ window.__ModuleLoader__.load({
|
|
|
2064
2081
|
disabled: busy,
|
|
2065
2082
|
placeholder: t('bodyPlaceholder'),
|
|
2066
2083
|
onChange: (event) => onPatch({ body: event.target.value }),
|
|
2067
|
-
onBlur: onCommit,
|
|
2068
2084
|
}),
|
|
2069
2085
|
(draft.problems ?? []).length > 0
|
|
2070
2086
|
? h(
|
|
@@ -2074,7 +2090,17 @@ window.__ModuleLoader__.load({
|
|
|
2074
2090
|
...(draft.problems ?? []).map((line, index) => h('div', { key: index, style: L.error }, line)),
|
|
2075
2091
|
)
|
|
2076
2092
|
: null,
|
|
2077
|
-
h(
|
|
2093
|
+
h(
|
|
2094
|
+
'div',
|
|
2095
|
+
{ style: { ...L.bar, justifyContent: 'space-between', marginTop: '4px' } },
|
|
2096
|
+
h('span', { style: L.hint }, `${t('saveHint')} · ${t('nameDrivesFile')}`),
|
|
2097
|
+
h(
|
|
2098
|
+
'div',
|
|
2099
|
+
{ style: L.bar },
|
|
2100
|
+
h(Btn, { variant: 'toolbar', disabled: busy, onClick: onCancel }, t('cancel')),
|
|
2101
|
+
h(Btn, { variant: 'primary', disabled: busy || dirty === false, onClick: onSave }, t('save')),
|
|
2102
|
+
),
|
|
2103
|
+
),
|
|
2078
2104
|
),
|
|
2079
2105
|
);
|
|
2080
2106
|
}
|
|
@@ -2291,9 +2317,9 @@ window.__ModuleLoader__.load({
|
|
|
2291
2317
|
'aria-label': t('preview'),
|
|
2292
2318
|
/*
|
|
2293
2319
|
* Not disabled while `busy`. Reading does not fight the writer, and
|
|
2294
|
-
*
|
|
2295
|
-
*
|
|
2296
|
-
*
|
|
2320
|
+
* an explicit-save editor means the click never starts a write —
|
|
2321
|
+
* the preview shows the last saved version, unsaved keystrokes
|
|
2322
|
+
* stay in the editor where they belong.
|
|
2297
2323
|
*/
|
|
2298
2324
|
disabled: locked,
|
|
2299
2325
|
style: { padding: '0 6px' },
|
|
@@ -2713,6 +2739,8 @@ window.__ModuleLoader__.load({
|
|
|
2713
2739
|
width: '860px',
|
|
2714
2740
|
t,
|
|
2715
2741
|
onClose: onCancelDraft,
|
|
2742
|
+
// 与人设编辑器同一档:草稿只认显式的取消/保存。
|
|
2743
|
+
locked: true,
|
|
2716
2744
|
},
|
|
2717
2745
|
h(TeamDraftCard, {
|
|
2718
2746
|
draft,
|
|
@@ -3635,7 +3663,8 @@ window.__ModuleLoader__.load({
|
|
|
3635
3663
|
onImportClick,
|
|
3636
3664
|
onDownloadTemplate,
|
|
3637
3665
|
onPatchDraft,
|
|
3638
|
-
|
|
3666
|
+
onSave,
|
|
3667
|
+
dirty,
|
|
3639
3668
|
onCloseDraft,
|
|
3640
3669
|
page,
|
|
3641
3670
|
pageSize,
|
|
@@ -3803,8 +3832,10 @@ window.__ModuleLoader__.load({
|
|
|
3803
3832
|
width: '820px',
|
|
3804
3833
|
t,
|
|
3805
3834
|
onClose: onCloseDraft,
|
|
3835
|
+
// 点遮罩、Esc 都不收:草稿只认显式的取消/保存。
|
|
3836
|
+
locked: true,
|
|
3806
3837
|
},
|
|
3807
|
-
h(Editor, { draft, t, busy, onPatch: onPatchDraft,
|
|
3838
|
+
h(Editor, { draft, t, busy, onPatch: onPatchDraft, onSave, onCancel: onCloseDraft, dirty, tall: true }),
|
|
3808
3839
|
)
|
|
3809
3840
|
: null,
|
|
3810
3841
|
pageCount > 1
|
|
@@ -3935,12 +3966,12 @@ window.__ModuleLoader__.load({
|
|
|
3935
3966
|
const idAlias = React.useRef(new Map());
|
|
3936
3967
|
const fileInput = React.useRef(null);
|
|
3937
3968
|
/**
|
|
3938
|
-
* The
|
|
3969
|
+
* The explicit save currently in flight, if any.
|
|
3939
3970
|
*
|
|
3940
|
-
*
|
|
3941
|
-
*
|
|
3942
|
-
*
|
|
3943
|
-
*
|
|
3971
|
+
* Saves start only from the editor's Save button now, so there is no
|
|
3972
|
+
* blur race to lose — but a second click on Save while one write is
|
|
3973
|
+
* still running must not start a duplicate, and `openPreview` keeps
|
|
3974
|
+
* waiting on this slot so a save and a read can never interleave.
|
|
3944
3975
|
*/
|
|
3945
3976
|
const savingRef = React.useRef(null);
|
|
3946
3977
|
|
|
@@ -4513,6 +4544,13 @@ window.__ModuleLoader__.load({
|
|
|
4513
4544
|
savedKey: written,
|
|
4514
4545
|
});
|
|
4515
4546
|
}
|
|
4547
|
+
/*
|
|
4548
|
+
* 保存成功即收窗。写盘已经完成,草稿没有值得留下的东西——这与
|
|
4549
|
+
* 团队编辑器的「成功即 `setTeamDraft(null)`」是同一条约定;留窗
|
|
4550
|
+
* 是失焦自动保存时代的习惯,那时窗口必须开着等下一次失焦。
|
|
4551
|
+
* 失败路径不动:弹窗留在原地,错误贴在窗内。
|
|
4552
|
+
*/
|
|
4553
|
+
putDraft(null);
|
|
4516
4554
|
await load();
|
|
4517
4555
|
await loadRoster();
|
|
4518
4556
|
} catch (failure) {
|
|
@@ -4527,7 +4565,13 @@ window.__ModuleLoader__.load({
|
|
|
4527
4565
|
}
|
|
4528
4566
|
}
|
|
4529
4567
|
|
|
4530
|
-
/**
|
|
4568
|
+
/**
|
|
4569
|
+
* 编辑器「保存」按钮的唯一入口。
|
|
4570
|
+
*
|
|
4571
|
+
* 曾经失焦也会走到这里(blur → commit),如今唯一的调用方是按钮:
|
|
4572
|
+
* 写盘必须是人按下去的那个动作。`save` 自己做去重——草稿与上一次
|
|
4573
|
+
* 保存一致时不发请求——所以连点两下、或者没改就点,都是空操作。
|
|
4574
|
+
*/
|
|
4531
4575
|
const commit = React.useCallback(() => {
|
|
4532
4576
|
const current = draftRef.current;
|
|
4533
4577
|
if (current === null || current.loading === true) return;
|
|
@@ -4540,8 +4584,8 @@ window.__ModuleLoader__.load({
|
|
|
4540
4584
|
});
|
|
4541
4585
|
}, []);
|
|
4542
4586
|
|
|
4587
|
+
/* 关窗即放弃:没有点「保存」的改动一个字都不落盘。 */
|
|
4543
4588
|
function closeRow() {
|
|
4544
|
-
commit();
|
|
4545
4589
|
showRow(null);
|
|
4546
4590
|
putDraft(null);
|
|
4547
4591
|
setDialog(null);
|
|
@@ -4560,8 +4604,8 @@ window.__ModuleLoader__.load({
|
|
|
4560
4604
|
const id = item.id;
|
|
4561
4605
|
setPeek({ id, name: item.name ?? id, loading: true, text: '', error: '' });
|
|
4562
4606
|
try {
|
|
4563
|
-
//
|
|
4564
|
-
//
|
|
4607
|
+
// A save pressed moments ago may still be in flight; reading first
|
|
4608
|
+
// would answer with the previous version of the file.
|
|
4565
4609
|
if (savingRef.current !== null) await savingRef.current;
|
|
4566
4610
|
const payload = await post({ op: 'get', id, assembled: true });
|
|
4567
4611
|
setPeek((prev) =>
|
|
@@ -4583,7 +4627,6 @@ window.__ModuleLoader__.load({
|
|
|
4583
4627
|
closeRow();
|
|
4584
4628
|
return;
|
|
4585
4629
|
}
|
|
4586
|
-
commit();
|
|
4587
4630
|
setDialog(null);
|
|
4588
4631
|
setNote(null);
|
|
4589
4632
|
showRow(item.id);
|
|
@@ -4631,7 +4674,6 @@ window.__ModuleLoader__.load({
|
|
|
4631
4674
|
closeRow();
|
|
4632
4675
|
return;
|
|
4633
4676
|
}
|
|
4634
|
-
commit();
|
|
4635
4677
|
setDialog(null);
|
|
4636
4678
|
setNote(null);
|
|
4637
4679
|
showRow(NEW);
|
|
@@ -4658,7 +4700,6 @@ window.__ModuleLoader__.load({
|
|
|
4658
4700
|
closeRow();
|
|
4659
4701
|
return;
|
|
4660
4702
|
}
|
|
4661
|
-
commit();
|
|
4662
4703
|
showRow(SKELETON);
|
|
4663
4704
|
setNote(null);
|
|
4664
4705
|
putSkeletonDraft({ text: '', slots: 0, nameSlots: 0, problems: [], rev: null, loading: true });
|
|
@@ -5569,8 +5610,10 @@ window.__ModuleLoader__.load({
|
|
|
5569
5610
|
onImportClick: () => fileInput.current?.click(),
|
|
5570
5611
|
onDownloadTemplate: downloadTemplate,
|
|
5571
5612
|
onPatchDraft: patchDraft,
|
|
5572
|
-
|
|
5573
|
-
|
|
5613
|
+
// 显式保存:只有按钮会写盘。关窗(onCloseDraft)丢掉未保存的改动。
|
|
5614
|
+
onSave: commit,
|
|
5615
|
+
dirty: draft !== null && draft.savedKey !== undefined && draftKeyOf(draft) !== draft.savedKey,
|
|
5616
|
+
// Closing the editor window: drop the draft without writing.
|
|
5574
5617
|
onCloseDraft: closeRow,
|
|
5575
5618
|
onOpenSkeleton: openSkeleton,
|
|
5576
5619
|
skeletonDraft,
|
package/package.json
CHANGED
package/panel-ops.js
CHANGED
|
@@ -171,13 +171,34 @@ export function installPanelOps(ctx, deps) {
|
|
|
171
171
|
* 回答的问题——`registryToken` 保持指向我们登记时的那一个,复核时身份对不上
|
|
172
172
|
* 就一律回 `unreadable`。
|
|
173
173
|
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
174
|
+
* 解析时连续读两次,是为了验证服务面给得出稳定身份。这里有个真宿主
|
|
175
|
+
* (2026-11-01 诊断落盘实锤)教会我们的坑:cordis 对带 tracker 的服务,
|
|
176
|
+
* 每次属性访问都现包一层 traceable 代理(`createTraceable`)——裸比对象
|
|
177
|
+
* 身份,两次**永远**不相等,令牌会被永久置空,此后所有「确认摘除」都是
|
|
178
|
+
* `unreadable`,改一个人设就弹一次「注销失败」。所以身份比的是解包后的
|
|
179
|
+
* 底层服务对象(cordis 在代理上留了官方开瓶器,见 {@link registryIdentity});
|
|
180
|
+
* 底层两次都同一个,才发令牌。宁可全部看不清,也不要拿一个每次访问都
|
|
181
|
+
* 换底的引用去当证据。
|
|
177
182
|
*/
|
|
178
183
|
let registryRef = null;
|
|
179
184
|
let registryToken = null;
|
|
180
185
|
|
|
186
|
+
/**
|
|
187
|
+
* 解到 cordis traceable 包装下的底层服务对象。
|
|
188
|
+
*
|
|
189
|
+
* cordis 的 traceable 代理专门留了这个读口:读
|
|
190
|
+
* `Symbol.for("cordis.original")` 返回代理目标(真正的服务对象);不是
|
|
191
|
+
* 包装就是普通对象,符号读出来是 `undefined`,原样返回。用全局注册表
|
|
192
|
+
* 符号而不是 import,是为了不对宿主的模块布局做硬依赖。
|
|
193
|
+
*
|
|
194
|
+
* @param {object} value 属性访问刚拿到的(可能是包装的)服务对象
|
|
195
|
+
* @returns {object} 底层服务对象
|
|
196
|
+
*/
|
|
197
|
+
function registryIdentity(value) {
|
|
198
|
+
const inner = value[Symbol.for("cordis.original")];
|
|
199
|
+
return inner === undefined || inner === null ? value : inner;
|
|
200
|
+
}
|
|
201
|
+
|
|
181
202
|
/**
|
|
182
203
|
* @returns {object | null} 本实例登记用的 registry(可能还没就绪)
|
|
183
204
|
*/
|
|
@@ -186,8 +207,9 @@ export function installPanelOps(ctx, deps) {
|
|
|
186
207
|
const first = ctx.agentPresets;
|
|
187
208
|
if (first === undefined || first === null) return null;
|
|
188
209
|
const second = ctx.agentPresets;
|
|
210
|
+
const identity = registryIdentity(first);
|
|
189
211
|
registryRef = first;
|
|
190
|
-
registryToken =
|
|
212
|
+
registryToken = identity === registryIdentity(second) ? registryTokenOf(identity) : null;
|
|
191
213
|
return registryRef;
|
|
192
214
|
}
|
|
193
215
|
|