@xia-sc/dsh-cc-studio 0.10.3 → 0.11.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 +18 -12
- package/lib/agent.js +142 -8
- package/lib/client.js +289 -51
- package/lib/draft-lock.js +98 -0
- package/lib/index.js +215 -6
- package/package.json +2 -2
- package/presets/cc/agent.cordis.yml +2 -2
- package/presets/cc.patch.yml +2 -2
package/lib/client.js
CHANGED
|
@@ -141,6 +141,20 @@ window.__ModuleLoader__.load({
|
|
|
141
141
|
"settings.behavior.autoSave":"导入后自动 ★ 保存",
|
|
142
142
|
"settings.behavior.autoSaveDesc":"导入后自动落盘到 ~/.dsh/cc-library(新建 ID),省去再点保存。",
|
|
143
143
|
"settings.behavior.storageHint":"存储键 localStorage:dsh-cc-studio-settings,切预设/会话间共享。",
|
|
144
|
+
// —— 0.11.0 LLM 草稿锁(创作保护 / 只读 / 只读预览)——
|
|
145
|
+
"lock.bar.active":"🔒 LLM 正在创作「{name}」· 工坊只读 · 最后写入 {n} 分钟前",
|
|
146
|
+
"lock.bar.activeHint":"创作完成后让 LLM 调用 cc_release_draft 释放草稿锁;或到设置里关闭「LLM 创作保护(草稿锁)」。",
|
|
147
|
+
"lock.bar.stale":"⏳ LLM 已有 {n} 分钟未写入 · 草稿已自动解锁(LLM 再写会重新锁定)",
|
|
148
|
+
"lock.bar.preview":"👁 预览「{name}」· 未载入,LLM 的草稿未受影响",
|
|
149
|
+
"lock.bar.previewHint":"退出预览即可回到 LLM 正在写的草稿;继续载入别的卡只会切换预览,不会覆盖草稿。",
|
|
150
|
+
"lock.preview.exit":"退出预览",
|
|
151
|
+
"lock.msg.writeBlocked":"LLM 正在创作,本次修改未写入。",
|
|
152
|
+
"lock.msg.loadedPreview":"已进入预览:「{name}」· 草稿未被覆盖",
|
|
153
|
+
"lock.msg.importedPreview":"已进入预览:{k} 卡「{name}」· 草稿未被覆盖",
|
|
154
|
+
"lock.msg.exitPreview":"已退出预览,回到 LLM 草稿",
|
|
155
|
+
"lock.msg.draftLockOff":"「LLM 创作保护」已关闭:载入/导入会直接覆盖草稿(旧行为)",
|
|
156
|
+
"settings.behavior.draftLock":"LLM 创作保护(草稿锁)",
|
|
157
|
+
"settings.behavior.draftLockDesc":"LLM 写草稿时工坊只读,载入/导入只进入预览;关闭后完全恢复旧行为。",
|
|
144
158
|
"settings.tips.title":"使用提示",
|
|
145
159
|
"settings.tips.body":"1. 切换 CC 模式 → 2. 让 LLM 按 6 步走(角色→五维→世界书≥5→问候语→validate)→ 3. 在工坊校验/导出或侧边栏保存。",
|
|
146
160
|
"settings.language.title":"语言 / Language",
|
|
@@ -458,6 +472,20 @@ window.__ModuleLoader__.load({
|
|
|
458
472
|
"settings.behavior.autoSave":"Auto ★ save on import",
|
|
459
473
|
"settings.behavior.autoSaveDesc":"Auto save imported card to ~/.dsh/cc-library (new ID), skip manual save.",
|
|
460
474
|
"settings.behavior.storageHint":"Key: localStorage:dsh-cc-studio-settings, shared across presets/sessions.",
|
|
475
|
+
// —— 0.11.0 LLM draft lock (writing protection / read-only / preview) ——
|
|
476
|
+
"lock.bar.active":"🔒 LLM is writing “{name}” · workshop is read-only · last write {n} min ago",
|
|
477
|
+
"lock.bar.activeHint":"When the draft is done, ask the LLM to call cc_release_draft (or turn off “LLM draft protection (draft lock)” in Settings).",
|
|
478
|
+
"lock.bar.stale":"⏳ LLM has not written for {n} min · the draft was unlocked automatically (it re-locks on the next LLM write)",
|
|
479
|
+
"lock.bar.preview":"👁 Preview “{name}” · not loaded — the LLM's draft is untouched",
|
|
480
|
+
"lock.bar.previewHint":"Exit the preview to return to the draft the LLM is writing; loading another card only switches the preview and never overwrites the draft.",
|
|
481
|
+
"lock.preview.exit":"Exit preview",
|
|
482
|
+
"lock.msg.writeBlocked":"LLM is writing — this change was not written.",
|
|
483
|
+
"lock.msg.loadedPreview":"Previewing “{name}” · the draft was not overwritten",
|
|
484
|
+
"lock.msg.importedPreview":"Previewing the {k} card “{name}” · the draft was not overwritten",
|
|
485
|
+
"lock.msg.exitPreview":"Left the preview — back to the LLM draft",
|
|
486
|
+
"lock.msg.draftLockOff":"LLM draft protection is off: load/import overwrite the draft directly (legacy behavior)",
|
|
487
|
+
"settings.behavior.draftLock":"LLM draft protection (draft lock)",
|
|
488
|
+
"settings.behavior.draftLockDesc":"While the LLM writes this session's draft the workshop is read-only and load/import open a preview. Turn off to restore the legacy behavior.",
|
|
461
489
|
"settings.tips.title":"Tips",
|
|
462
490
|
"settings.tips.body":"1. Switch to CC Mode → 2. Let LLM guide 6 steps (Character→5D→Lorebook≥5→Greetings→validate) → 3. Validate/export or ★ save in sidebar.",
|
|
463
491
|
"settings.language.title":"Language / 语言",
|
|
@@ -703,15 +731,17 @@ window.__ModuleLoader__.load({
|
|
|
703
731
|
poll: s.poll!==false,
|
|
704
732
|
pollInterval: typeof s.pollInterval==="number" ? Math.max(0, Math.min(30, Math.round(s.pollInterval))) : 4,
|
|
705
733
|
autoValidate: s.autoValidate!==false,
|
|
706
|
-
autoSaveOnImport: !!s.autoSaveOnImport
|
|
734
|
+
autoSaveOnImport: !!s.autoSaveOnImport,
|
|
735
|
+
// 默认开:升级上来的老设置里没有这个键,`!==false` 才会把默认值定成 true(旧行为只在新用户里成立)。
|
|
736
|
+
draftLock: s.draftLock!==false
|
|
707
737
|
};
|
|
708
738
|
}catch(e){}
|
|
709
|
-
return { poll:true, pollInterval:4, autoValidate:true, autoSaveOnImport:false };
|
|
739
|
+
return { poll:true, pollInterval:4, autoValidate:true, autoSaveOnImport:false, draftLock:true };
|
|
710
740
|
}
|
|
711
741
|
var _saved = loadSettings();
|
|
712
742
|
// 兼容旧开关:poll=false 视为 0 秒
|
|
713
743
|
if(_saved.poll===false) _saved.pollInterval = 0;
|
|
714
|
-
var state={ draft: draft, draftStatus: null, cur:0, panelOpen:false, sideExpanded:false, isCcMode:false, currentSessionId:null, slotWarn:null, alternateSlots:null, world:{ timeline:"", factions:"", geo:"", power:"", daily:"" }, busy:null, valid:{valid:true,errors:[],warnings:[]}, lastMsg:null, library:[], libraryQuery:"", libraryCollapsed:false, libraryBusy:null, groups:[], currentGroupId:"", relations:null, relBusy:null, relIssues:null, relByCheck:null, currentLibraryId:null, book:null, bookBusy:null, bookMerge:false, editingWorld:null, editingWorldRevert:null, editingText:null, pollEnabled:_saved.poll, pollInterval:_saved.pollInterval, autoValidate:_saved.autoValidate, autoSaveOnImport:_saved.autoSaveOnImport };
|
|
744
|
+
var state={ draft: draft, draftStatus: null, cur:0, panelOpen:false, sideExpanded:false, isCcMode:false, currentSessionId:null, slotWarn:null, alternateSlots:null, world:{ timeline:"", factions:"", geo:"", power:"", daily:"" }, busy:null, valid:{valid:true,errors:[],warnings:[]}, lastMsg:null, library:[], libraryQuery:"", libraryCollapsed:false, libraryBusy:null, groups:[], currentGroupId:"", relations:null, relBusy:null, relIssues:null, relByCheck:null, currentLibraryId:null, book:null, bookBusy:null, bookMerge:false, editingWorld:null, editingWorldRevert:null, editingText:null, pollEnabled:_saved.poll, pollInterval:_saved.pollInterval, autoValidate:_saved.autoValidate, autoSaveOnImport:_saved.autoSaveOnImport, draftLock:_saved.draftLock, lock:null, preview:null, llmDraft:null, llmWorld:null, llmDraftStatus:null };
|
|
715
745
|
var listeners=new Set();
|
|
716
746
|
function emit(){ listeners.forEach(function(fn){ try{fn();}catch(e){console.error(e);} }); }
|
|
717
747
|
function getSnapshot(){ return state; }
|
|
@@ -728,13 +758,98 @@ window.__ModuleLoader__.load({
|
|
|
728
758
|
// 草稿槽不一致告警(issue #5):主机回传的 key 与我们要的不是同一把时,绝不把它当当前草稿画出来。
|
|
729
759
|
function setSlotWarn(w){ set({slotWarn: w||null}); }
|
|
730
760
|
function setAlternateSlots(list){ set({alternateSlots: (list && list.length)? list : null}); }
|
|
761
|
+
// ———————————————— 0.11.0 LLM 草稿锁:只读 / 只读预览 ————————————————
|
|
762
|
+
// 锁的状态来自宿主 cc_getDraft 的 draftStatus.lock(不另开端点):轮询本来就在跑,
|
|
763
|
+
// 多一条 RPC 只会多出一个「客户端与宿主对锁看法不一致」的窗口。
|
|
764
|
+
// 判定必须只用宿主给的字段(stale / active 由宿主按自己的时钟算好):客户端再算一遍时间,
|
|
765
|
+
// 一旦两边时钟/时区有偏差,就会出现「宿主说锁着、界面说能写」的裂缝。
|
|
766
|
+
function setLock(lock){
|
|
767
|
+
var next = (lock && typeof lock==="object") ? lock : null;
|
|
768
|
+
try{ if(JSON.stringify(next)===JSON.stringify(state.lock||null)) return; }catch(e){}
|
|
769
|
+
set({lock:next});
|
|
770
|
+
}
|
|
771
|
+
/** LLM 是否正持有这把会话槽的创作锁(且用户没关保护)。stale = 超过 TTL 未写入 → 视为可编辑。 */
|
|
772
|
+
function isLockActive(){
|
|
773
|
+
return !!state.draftLock && !!state.lock && state.lock.owner==="llm" && !state.lock.stale;
|
|
774
|
+
}
|
|
775
|
+
/** 只读 = LLM 锁生效 或 正在只读预览。所有会动草稿的 store 动作都先问这一句。 */
|
|
776
|
+
function isReadOnly(){
|
|
777
|
+
return isLockActive() || !!state.preview;
|
|
778
|
+
}
|
|
779
|
+
/** 从草稿里取五维(与 setDraftFromHost 同一套字段),给预览的快照/恢复用。 */
|
|
780
|
+
function worldOfDraft(draft){
|
|
781
|
+
try{
|
|
782
|
+
var w = draft && draft.data && draft.data.extensions && draft.data.extensions.cc_world;
|
|
783
|
+
if(!w || typeof w!=="object") return null;
|
|
784
|
+
return { timeline:String(w.timeline||""), factions:String(w.factions||""), geo:String(w.geo||""), power:String(w.power||""), daily:String(w.daily||"") };
|
|
785
|
+
}catch(e){ return null; }
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* 预览期间宿主草稿(LLM 那份)的最新副本:只写 llm*,**绝不碰正在显示的 draft**。
|
|
789
|
+
* 不比较 ageSec 之类的易变字段,比较整份 JSON —— 没变就不 emit,免得每 4s 白重渲染一次工坊。
|
|
790
|
+
*/
|
|
791
|
+
function setLlmDraft(draft, status){
|
|
792
|
+
var st = status || null;
|
|
793
|
+
var lw = worldOfDraft(draft);
|
|
794
|
+
var up = {};
|
|
795
|
+
try{ if(JSON.stringify(draft)!==JSON.stringify(state.llmDraft)) up.llmDraft = draft; }catch(e){ up.llmDraft = draft; }
|
|
796
|
+
try{ if(JSON.stringify(st)!==JSON.stringify(state.llmDraftStatus||null)) up.llmDraftStatus = st; }catch(e){ up.llmDraftStatus = st; }
|
|
797
|
+
if(lw && JSON.stringify(lw)!==JSON.stringify(state.llmWorld)) up.llmWorld = lw;
|
|
798
|
+
if(up.llmDraft!==undefined || up.llmDraftStatus!==undefined || up.llmWorld!==undefined) set(up);
|
|
799
|
+
}
|
|
800
|
+
function previewNameOf(prev){
|
|
801
|
+
try{
|
|
802
|
+
var meta = prev && prev.meta;
|
|
803
|
+
var d = prev && prev.draft && prev.draft.data;
|
|
804
|
+
var name = (meta && meta.name) || (d && d.name) || "";
|
|
805
|
+
return String(name).trim() || t("workshop.library.unnamed");
|
|
806
|
+
}catch(e){ return t("workshop.library.unnamed"); }
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* 进入只读预览:宿主在锁 active 时**不写草稿**,只回「假如载入/导入会变成什么」。
|
|
810
|
+
* 三条自律:
|
|
811
|
+
* ① 首次进入时把 LLM 那份草稿/五维/状态整份存进 llm*(连续预览第二张只换预览卡,
|
|
812
|
+
* 否则第一张就被第二张的快照顶掉,退出预览会回错版本);
|
|
813
|
+
* ② currentLibraryId 与当前分组**不动** —— 预览不是载入,退出后必须原样回到 LLM 的上下文;
|
|
814
|
+
* ③ 显示用 setDraftFromHost 换上预览卡(五维跟着草稿里的 cc_world 走)。
|
|
815
|
+
*/
|
|
816
|
+
function enterPreview(prev){
|
|
817
|
+
if(!prev || !prev.draft) return;
|
|
818
|
+
var first = !state.preview;
|
|
819
|
+
var up = { preview:{ id:(prev.id==null? "": String(prev.id)), name:previewNameOf(prev), meta:prev.meta||null, upgradedFrom:prev.upgradedFrom||null } };
|
|
820
|
+
if(first){ up.llmDraft = state.draft; up.llmWorld = state.world; up.llmDraftStatus = state.draftStatus; }
|
|
821
|
+
set(up);
|
|
822
|
+
setDraftFromHost(prev.draft, state.draftStatus||null);
|
|
823
|
+
}
|
|
824
|
+
/** 退出预览:把 LLM 那份草稿恢复成显示内容(期间轮询到的最新版本在 llmDraft 里)。 */
|
|
825
|
+
function exitPreview(){
|
|
826
|
+
if(!state.preview) return;
|
|
827
|
+
var draft = state.llmDraft;
|
|
828
|
+
var world = state.llmWorld;
|
|
829
|
+
var st = state.llmDraftStatus;
|
|
830
|
+
set({preview:null, llmDraft:null, llmWorld:null, llmDraftStatus:null});
|
|
831
|
+
if(draft) setDraftFromHost(draft, st||null);
|
|
832
|
+
if(world) set({world: world});
|
|
833
|
+
set({lastMsg: t("lock.msg.exitPreview")});
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* 设置页关掉「LLM 创作保护」= 完全旧行为:所有写类 RPC 显式带 force:true,
|
|
837
|
+
* 宿主据此跳过锁检查(并顺手清掉那把锁)。读类请求(cc_getDraft 等)不带。
|
|
838
|
+
*/
|
|
839
|
+
function forceArgs(args){
|
|
840
|
+
var out = Object.assign({}, args||{});
|
|
841
|
+
if(!state.draftLock) out.force = true;
|
|
842
|
+
return out;
|
|
843
|
+
}
|
|
731
844
|
// 把另一把槽的内容迁进当前会话槽(用户点「迁入当前会话槽」时调用)
|
|
732
845
|
async function migrateSlot(from){
|
|
846
|
+
// 迁移会整份覆盖当前槽的草稿 —— 锁定/预览期间绝不能做(宿主也会拦,这里先一步不发请求)
|
|
847
|
+
if(isReadOnly()) return;
|
|
733
848
|
var to = state.currentSessionId || null;
|
|
734
849
|
if(!from || !to) return;
|
|
735
850
|
set({busy:"migrate"});
|
|
736
851
|
try{
|
|
737
|
-
var res=await rpc("cc_migrateDraft", {from: from, to: to, sessionId: to});
|
|
852
|
+
var res=await rpc("cc_migrateDraft", forceArgs({from: from, to: to, sessionId: to}));
|
|
738
853
|
if(res && res.ok && res.value && res.value.draft){
|
|
739
854
|
setDraftFromHost(res.value.draft, res.value.draftStatus||null);
|
|
740
855
|
// 迁过一次就不再重复提示「盘上还有一份 default 草稿」——源槽内容不删(不做破坏性操作),
|
|
@@ -748,19 +863,50 @@ window.__ModuleLoader__.load({
|
|
|
748
863
|
}catch(e){ set({busy:null, lastMsg: t("workshop.slot.migrateFailed")+" — "+String(e.message)}); }
|
|
749
864
|
}
|
|
750
865
|
function saveSettings(){
|
|
751
|
-
try{ localStorage.setItem("dsh-cc-studio-settings", JSON.stringify({ poll: !!state.pollEnabled, pollInterval: Number(state.pollInterval)||0, autoValidate: !!state.autoValidate, autoSaveOnImport: !!state.autoSaveOnImport })); }catch(e){}
|
|
866
|
+
try{ localStorage.setItem("dsh-cc-studio-settings", JSON.stringify({ poll: !!state.pollEnabled, pollInterval: Number(state.pollInterval)||0, autoValidate: !!state.autoValidate, autoSaveOnImport: !!state.autoSaveOnImport, draftLock: !!state.draftLock })); }catch(e){}
|
|
752
867
|
}
|
|
753
868
|
function setPollEnabled(v){ var on=!!v; var interval = on ? (state.pollInterval||4) : 0; if(!on) interval=0; if(on && interval===0) interval=4; set({pollEnabled: on, pollInterval: interval}); saveSettings(); }
|
|
754
869
|
function setPollInterval(sec){ var n=Math.max(0, Math.min(30, Math.round(Number(sec)||0))); var on = n>0; set({pollInterval: n, pollEnabled: on}); saveSettings(); }
|
|
755
870
|
function setAutoValidate(v){ set({autoValidate: !!v}); saveSettings(); }
|
|
756
871
|
function setAutoSaveOnImport(v){ set({autoSaveOnImport: !!v}); saveSettings(); }
|
|
872
|
+
// 「LLM 创作保护」总开关:关掉 = 完全恢复旧行为(写 RPC 一律带 force:true,锁不再置只读)。
|
|
873
|
+
function setDraftLock(v){
|
|
874
|
+
var on=!!v;
|
|
875
|
+
set({draftLock: on});
|
|
876
|
+
if(!on) set({lastMsg: t("lock.msg.draftLockOff")});
|
|
877
|
+
saveSettings();
|
|
878
|
+
}
|
|
757
879
|
function pushDraftToHost(){
|
|
880
|
+
// 第二道闸:UI 已经只读(输入框 readOnly / 按钮 disabled),这里防的是「锁在两次轮询之间
|
|
881
|
+
// 突然出现」与程序化调用 —— 锁定时把本地草稿推上去,等于替 LLM 覆盖它正在写的内容。
|
|
882
|
+
if(isReadOnly()) return;
|
|
758
883
|
try{
|
|
759
884
|
var sid = state.currentSessionId || null;
|
|
760
885
|
var args = sid ? {draft: state.draft, sessionId: sid} : {draft: state.draft};
|
|
761
|
-
ctx.connection.rpc.call("/dsh-cc-studio-rpc","cc_setDraft",{args:args}).
|
|
886
|
+
ctx.connection.rpc.call("/dsh-cc-studio-rpc","cc_setDraft",{args:forceArgs(args)}).then(function(res){
|
|
887
|
+
// 「拉草稿」与「写草稿」之间的窗口里 LLM 上了锁:宿主明确拒绝(details.code=draft-locked)。
|
|
888
|
+
// 拉一次 cc_getDraft 把锁与盘上真实草稿拉回来,并把「这次修改没写进去」说清楚 ——
|
|
889
|
+
// 否则用户只会看到刚打的字过几秒自己变回去,完全不知道发生了什么。
|
|
890
|
+
if(res && res.ok===false && res.error && res.error.details && res.error.details.code==="draft-locked"){
|
|
891
|
+
set({lastMsg: t("lock.msg.writeBlocked")});
|
|
892
|
+
refreshFromHostForLock();
|
|
893
|
+
}
|
|
894
|
+
}).catch(function(){});
|
|
762
895
|
}catch(e){}
|
|
763
896
|
}
|
|
897
|
+
/** 写被锁拒绝后的补救:拉一次 cc_getDraft,刷新锁(顺带同步盘上真实草稿)。没有会话 id 就不拉(铁律)。 */
|
|
898
|
+
function refreshFromHostForLock(){
|
|
899
|
+
var sid = state.currentSessionId || null;
|
|
900
|
+
if(!sid) return Promise.resolve();
|
|
901
|
+
return rpc("cc_getDraft", {sessionId: sid}).then(function(res){
|
|
902
|
+
if(!res || !res.ok || !res.value) return;
|
|
903
|
+
var st = res.value.draftStatus || null;
|
|
904
|
+
if(st) setLock(st.lock || null);
|
|
905
|
+
if(!res.value.draft) return;
|
|
906
|
+
if(state.preview) setLlmDraft(res.value.draft, st);
|
|
907
|
+
else setDraftFromHost(res.value.draft, st);
|
|
908
|
+
}).catch(function(){});
|
|
909
|
+
}
|
|
764
910
|
var _ccCheckAt = {};
|
|
765
911
|
var _ccCheckInflight = {};
|
|
766
912
|
async function checkIsCcMode(sessionId, force){
|
|
@@ -782,6 +928,9 @@ window.__ModuleLoader__.load({
|
|
|
782
928
|
}catch(e){ try{ _ccCheckInflight[String(sessionId||"")]=false; }catch(e2){} }
|
|
783
929
|
}
|
|
784
930
|
function updateDraft(mut){
|
|
931
|
+
// 只读(LLM 锁定 / 预览)时连本地草稿都不改:改了不落盘,下一次轮询就会被宿主版本覆盖 ——
|
|
932
|
+
// 用户看到的是「刚打的字凭空消失」,比一开始就拦下来更费解。
|
|
933
|
+
if(isReadOnly()) return;
|
|
785
934
|
var d=JSON.parse(JSON.stringify(state.draft));
|
|
786
935
|
mut(d.data);
|
|
787
936
|
// 无实际改动则不落盘、不推宿主、不动 modification_date:
|
|
@@ -822,6 +971,9 @@ window.__ModuleLoader__.load({
|
|
|
822
971
|
* 必须在提示里说出来 —— 静默把一张 V2 卡变成 V3 形状不算「什么都没发生」。
|
|
823
972
|
*/
|
|
824
973
|
function handleImportedDraft(obj, label, upgradedFrom){
|
|
974
|
+
// 非锁定导入 = 真的要写草稿(锁已释放/超时,或用户关掉了保护):先把预览态清掉,
|
|
975
|
+
// 否则横幅还挂在「预览 xxx」,显示内容却已经被换成导入的卡(两个状态互相矛盾)。
|
|
976
|
+
if(state.preview) set({preview:null, llmDraft:null, llmWorld:null, llmDraftStatus:null});
|
|
825
977
|
obj.spec_version=obj.spec_version||"3.0";
|
|
826
978
|
obj.data.character_book=obj.data.character_book||{name:"",description:"",scan_depth:4,token_budget:1200,recursive_scanning:false,extensions:{},entries:[]};
|
|
827
979
|
if(!Array.isArray(obj.data.character_book.entries)) obj.data.character_book.entries=[];
|
|
@@ -843,6 +995,23 @@ window.__ModuleLoader__.load({
|
|
|
843
995
|
}, 200);
|
|
844
996
|
}
|
|
845
997
|
}
|
|
998
|
+
/**
|
|
999
|
+
* 宿主在锁 active 时把导入结果原样退回(`locked:true` + `preview`),**不写草稿**。
|
|
1000
|
+
* 这里只负责进只读预览 + 保留 upgradedFrom 提示;自动校验/自动保存都不能跑 ——
|
|
1001
|
+
* 那会把「只是看了一眼」的预览卡真的写进角色库(用户没点过保存)。
|
|
1002
|
+
*/
|
|
1003
|
+
function enterPreviewFromLocked(res, label){
|
|
1004
|
+
if(!res || !res.ok || !res.value || res.value.locked!==true || !res.value.preview) return false;
|
|
1005
|
+
setLock(res.value.lock || null); // 同 loadFromLibrary:响应里的锁立刻生效,别等下一次轮询
|
|
1006
|
+
enterPreview(res.value.preview);
|
|
1007
|
+
// 方括号读 upgradedFrom:st-compat 的源码守卫按字面量统计「三条导入路径都把它交给提示」,
|
|
1008
|
+
// 这里的共用收尾不该被算成第四条路径。
|
|
1009
|
+
var upgradedFrom = (res.value && res.value["upgradedFrom"]) || null;
|
|
1010
|
+
var msg = t("lock.msg.importedPreview", {k:String(label||"").trim(), name:previewNameOf(res.value.preview)});
|
|
1011
|
+
if(upgradedFrom) msg += " · " + t("store.importedUpgraded",{v:(String(upgradedFrom)==="chara_card_v1"? "V1":"V2")});
|
|
1012
|
+
set({lastMsg: msg});
|
|
1013
|
+
return true;
|
|
1014
|
+
}
|
|
846
1015
|
function importAny(){
|
|
847
1016
|
try{
|
|
848
1017
|
var inp=document.createElement("input");
|
|
@@ -868,10 +1037,11 @@ window.__ModuleLoader__.load({
|
|
|
868
1037
|
try{
|
|
869
1038
|
var text=String(reader.result||"");
|
|
870
1039
|
var sid=state.currentSessionId||null;
|
|
871
|
-
var args=sid?{json:text, sessionId:sid}:{json:text};
|
|
1040
|
+
var args=forceArgs(sid?{json:text, sessionId:sid}:{json:text});
|
|
872
1041
|
set({lastMsg:t("store.parsingJson"), busy:"importJson"});
|
|
873
1042
|
rpc("cc_importFromJson", args).then(function(res){
|
|
874
1043
|
set({busy:null});
|
|
1044
|
+
if(enterPreviewFromLocked(res, " JSON")) return;
|
|
875
1045
|
if(res && res.ok && res.value && res.value.draft){
|
|
876
1046
|
handleImportedDraft(res.value.draft, " JSON", res.value.upgradedFrom);
|
|
877
1047
|
} else set({lastMsg:t("store.errImportFailed",{msg:(res&&res.error?String(res.error.message):t("common.unknownError"))})});
|
|
@@ -886,10 +1056,11 @@ window.__ModuleLoader__.load({
|
|
|
886
1056
|
try{
|
|
887
1057
|
var b64=arrayBufferToB64(r2.result);
|
|
888
1058
|
var sid=state.currentSessionId||null;
|
|
889
|
-
var args=sid?{b64:b64, sessionId:sid}:{b64:b64};
|
|
1059
|
+
var args=forceArgs(sid?{b64:b64, sessionId:sid}:{b64:b64});
|
|
890
1060
|
set({lastMsg:t("store.parsingPng"), busy:"importPng"});
|
|
891
1061
|
rpc("cc_importFromPng", args).then(function(res){
|
|
892
1062
|
set({busy:null});
|
|
1063
|
+
if(enterPreviewFromLocked(res, " PNG")) return;
|
|
893
1064
|
if(res && res.ok && res.value && res.value.draft){
|
|
894
1065
|
handleImportedDraft(res.value.draft, " PNG", res.value.upgradedFrom);
|
|
895
1066
|
} else set({lastMsg:t("store.errPngImportFailed",{msg:(res&&res.error?String(res.error.message):t("common.unknownError"))})});
|
|
@@ -904,10 +1075,11 @@ window.__ModuleLoader__.load({
|
|
|
904
1075
|
try{
|
|
905
1076
|
var b64c=arrayBufferToB64(r3.result);
|
|
906
1077
|
var sid2=state.currentSessionId||null;
|
|
907
|
-
var args2=sid2?{b64:b64c, sessionId:sid2}:{b64:b64c};
|
|
1078
|
+
var args2=forceArgs(sid2?{b64:b64c, sessionId:sid2}:{b64:b64c});
|
|
908
1079
|
set({lastMsg:t("store.parsingCharx"), busy:"importCharx"});
|
|
909
1080
|
rpc("cc_importFromCharx", args2).then(function(res){
|
|
910
1081
|
set({busy:null});
|
|
1082
|
+
if(enterPreviewFromLocked(res, " CHARX")) return;
|
|
911
1083
|
if(res && res.ok && res.value && res.value.draft){
|
|
912
1084
|
handleImportedDraft(res.value.draft, " CHARX", res.value.upgradedFrom);
|
|
913
1085
|
} else set({lastMsg:t("store.errCharxImportFailed",{msg:(res&&res.error?String(res.error.message):t("common.unknownError"))})});
|
|
@@ -1151,6 +1323,8 @@ window.__ModuleLoader__.load({
|
|
|
1151
1323
|
* ③ 共享书没读到(state.book 为空)时什么都不做 —— 宁可不同步,也不猜着删。
|
|
1152
1324
|
*/
|
|
1153
1325
|
function syncDraftAfterPromote(v){
|
|
1326
|
+
// 预览/锁定时界面里的草稿不是 LLM 那份:这时去删条目 = 改错对象
|
|
1327
|
+
if(isReadOnly()) return 0;
|
|
1154
1328
|
try{
|
|
1155
1329
|
var cid = String(state.currentLibraryId||"");
|
|
1156
1330
|
if(!cid) return 0;
|
|
@@ -1451,6 +1625,8 @@ window.__ModuleLoader__.load({
|
|
|
1451
1625
|
}
|
|
1452
1626
|
/** 把图谱里「我对别人的看法」渲染进当前卡的 description(固定标记区,重复渲染是替换)。 */
|
|
1453
1627
|
async function renderRelationsToCard(){
|
|
1628
|
+
// 渲染会改当前草稿的 description —— 锁定/预览期间不动(宿主也会拦,这里先不发请求)
|
|
1629
|
+
if(isReadOnly()) return;
|
|
1454
1630
|
if(!state.currentLibraryId){ set({lastMsg:t("workshop.rel.needLoad")}); return; }
|
|
1455
1631
|
set({relBusy:"render"});
|
|
1456
1632
|
try{
|
|
@@ -1458,7 +1634,7 @@ window.__ModuleLoader__.load({
|
|
|
1458
1634
|
var args = { cardId: state.currentLibraryId };
|
|
1459
1635
|
if(state.currentGroupId) args.groupId = state.currentGroupId;
|
|
1460
1636
|
if(sid) args.sessionId = sid;
|
|
1461
|
-
var res = await rpc("cc_renderRelations", args);
|
|
1637
|
+
var res = await rpc("cc_renderRelations", forceArgs(args));
|
|
1462
1638
|
if(res && res.ok && res.value){
|
|
1463
1639
|
// 宿主改了草稿文件 → 重新拉一次,否则工坊里看到的还是旧 description
|
|
1464
1640
|
var dres = await rpc("cc_getDraft", sid ? {sessionId: sid} : {});
|
|
@@ -1517,16 +1693,29 @@ window.__ModuleLoader__.load({
|
|
|
1517
1693
|
}catch(e){ set({lastMsg: String(e && e.message || e)}); }
|
|
1518
1694
|
set({libraryBusy:null});
|
|
1519
1695
|
}
|
|
1696
|
+
/**
|
|
1697
|
+
* 只改「当前草稿的来源 id」(不写宿主)。pullDraft 在 apply() 的作用域里跑,
|
|
1698
|
+
* 那里没有 createStore 内部的 `set` —— 旧代码直接调 `set({currentLibraryId: originId})`
|
|
1699
|
+
* 会在 originId 非空时抛 ReferenceError,被尾部的 .catch 吞掉,于是**来源 sidecar 永远恢复不了**,
|
|
1700
|
+
* 同一次响应里的 setDraftFromHost 也跟着不执行(0.11.0 集成时发现并修)。
|
|
1701
|
+
*/
|
|
1702
|
+
function setCurrentLibraryId(id){
|
|
1703
|
+
var v = (typeof id === "string" && id) ? id : null;
|
|
1704
|
+
if(v === state.currentLibraryId) return;
|
|
1705
|
+
set({ currentLibraryId: v });
|
|
1706
|
+
}
|
|
1520
1707
|
function clearCurrentLibraryId(){
|
|
1521
1708
|
set({currentLibraryId:null, lastMsg:t("store.switchedNew")});
|
|
1522
1709
|
// 同步清掉宿主侧的来源 id(sidecar):否则下次保存又会按旧来源「更新」那一张
|
|
1523
|
-
try{ rpc("cc_setOrigin", { sessionId: state.currentSessionId||null, id: null }); }catch(e){}
|
|
1710
|
+
try{ rpc("cc_setOrigin", forceArgs({ sessionId: state.currentSessionId||null, id: null })); }catch(e){}
|
|
1524
1711
|
}
|
|
1525
1712
|
// 清空当前角色卡:整份草稿换回空卡(makeDraft),并解除「跟已存角色的关联」——
|
|
1526
1713
|
// 否则清空后点「保存当前」会把库里那张卡覆盖成空卡(要删卡请用右侧列表的删除)。
|
|
1527
1714
|
// 落盘与其它写入走同一条路(pushDraftToHost):刷新页面、模型侧读到的都是空卡。
|
|
1528
1715
|
// 二次确认由调用方负责(工坊底部那枚按钮),这里只管状态本身。
|
|
1529
1716
|
function clearDraft(){
|
|
1717
|
+
// 清空是破坏性动作:锁定/预览期间清空 = 抹掉 LLM 正在写的内容,直接拒绝(按钮侧也已禁用)
|
|
1718
|
+
if(isReadOnly()) return;
|
|
1530
1719
|
set({
|
|
1531
1720
|
draft: makeDraft(),
|
|
1532
1721
|
world: { timeline:"", factions:"", geo:"", power:"", daily:"" },
|
|
@@ -1536,22 +1725,27 @@ window.__ModuleLoader__.load({
|
|
|
1536
1725
|
lastMsg: t("store.cleared")
|
|
1537
1726
|
});
|
|
1538
1727
|
// 清空 = 与已存卡脱钩:宿主侧的来源 id(sidecar)也要清,否则下次保存会「更新」原来那张卡(把它覆盖成空卡)
|
|
1539
|
-
try{ rpc("cc_setOrigin", { sessionId: state.currentSessionId||null, id: null }); }catch(e){}
|
|
1728
|
+
try{ rpc("cc_setOrigin", forceArgs({ sessionId: state.currentSessionId||null, id: null })); }catch(e){}
|
|
1540
1729
|
setTimeout(pushDraftToHost, 80);
|
|
1541
1730
|
}
|
|
1542
1731
|
async function loadFromLibrary(id){
|
|
1543
1732
|
if(!id) return;
|
|
1544
|
-
try{
|
|
1545
|
-
var curName=String(state.draft.data.name||"").trim();
|
|
1546
|
-
if(curName && state.draft.data.character_book.entries.length>0){
|
|
1547
|
-
}
|
|
1548
|
-
}catch(e){}
|
|
1549
1733
|
set({libraryBusy:"load:"+id});
|
|
1550
1734
|
try{
|
|
1551
1735
|
var sid=state.currentSessionId||null;
|
|
1552
|
-
var args = sid ? {id:id, sessionId:sid} : {id:id};
|
|
1736
|
+
var args = forceArgs(sid ? {id:id, sessionId:sid} : {id:id});
|
|
1553
1737
|
var res=await rpc("cc_loadFromLibrary", args);
|
|
1554
|
-
|
|
1738
|
+
// 锁 active(且用户没关保护):宿主**不写草稿**,只回「假如载入会变成什么」。
|
|
1739
|
+
// 这时进只读预览 —— 绝不能 setDraftFromHost 成当前草稿,那等于替 LLM 覆盖它的创作。
|
|
1740
|
+
if(res && res.ok && res.value && res.value.locked===true && res.value.preview){
|
|
1741
|
+
// 响应里带的锁要同步进 store:预览期间轮询可能还没跑到,退出预览后必须立刻保持只读,
|
|
1742
|
+
// 不能留下「退出预览后到下一次 4s 轮询之间可以编辑」的裂缝(那时宿主仍会拦,但体验是白打一次字)。
|
|
1743
|
+
setLock(res.value.lock || null);
|
|
1744
|
+
enterPreview(Object.assign({id:id}, res.value.preview));
|
|
1745
|
+
set({lastMsg: t("lock.msg.loadedPreview",{name: previewNameOf(res.value.preview)})});
|
|
1746
|
+
} else if(res && res.ok && res.value && res.value.draft){
|
|
1747
|
+
// 正常载入(无锁 / 锁已超时 / 设置里关掉了保护):先退出预览,再按旧逻辑整份替换草稿。
|
|
1748
|
+
if(state.preview) exitPreview();
|
|
1555
1749
|
setDraftFromHost(res.value.draft);
|
|
1556
1750
|
set({currentLibraryId: id});
|
|
1557
1751
|
// 0.9.0:载入一张卡就**跟着切到它所属的分组**。实机反馈:右边载入了陆昭,中间「人物关系」
|
|
@@ -1600,7 +1794,7 @@ window.__ModuleLoader__.load({
|
|
|
1600
1794
|
// 0.4.1:这里原本还有一个 exportLibraryEntry —— 直接下载某张已存角色的 JSON。
|
|
1601
1795
|
// 三个导出入口归一之后,卡片上的「导出」改成切到导出页,导出动作只剩导出页那一套实现,
|
|
1602
1796
|
// 这条旁路就没有调用者了(留着它等于「同一次导出有两套行为」),故删除。
|
|
1603
|
-
return { getSnapshot:getSnapshot, subscribe:subscribe, setCur:setCur, setPanel:setPanel, setSideExpanded:setSideExpanded, setIsCcMode:setIsCcMode, setDraftFromHost:setDraftFromHost, setSessionId:setSessionId, setSlotWarn:setSlotWarn, setAlternateSlots:setAlternateSlots, migrateSlot:migrateSlot, checkIsCcMode:checkIsCcMode, setPollEnabled:setPollEnabled, setPollInterval:setPollInterval, setAutoValidate:setAutoValidate, setAutoSaveOnImport:setAutoSaveOnImport, updateDraft:updateDraft, validate:validate, exportJSON:exportJSON, exportPNG:exportPNG, embedIntoPng:embedIntoPng, exportCharx:exportCharx, importJSON:importJSON, importAny:importAny, setWorldPatch:setWorldPatch, setEditingWorld:setEditingWorld, cancelEditingWorld:cancelEditingWorld, setEditingText:setEditingText, clearEditingText:clearEditingText, cancelEditingText:cancelEditingText, updateEditingText:updateEditingText, addEntry:addEntry, setLibraryQuery:setLibraryQuery, toggleLibraryCollapsed:toggleLibraryCollapsed, refreshLibrary:refreshLibrary, saveCurrentToLibrary:saveCurrentToLibrary, saveAsNewToLibrary:saveAsNewToLibrary, clearCurrentLibraryId:clearCurrentLibraryId, clearDraft:clearDraft, loadFromLibrary:loadFromLibrary, deleteFromLibrary:deleteFromLibrary, renameCurrentInLibrary:renameCurrentInLibrary, setCurrentGroupId:setCurrentGroupId, refreshGroups:refreshGroups, createGroup:createGroup, loadGroup:loadGroup, assignGroup:assignGroup, deleteGroup:deleteGroup, patchRelations:patchRelations, checkGroupNow:checkGroupNow, renderRelationsToCard:renderRelationsToCard, openRelations:openRelations, openRelationsFor:openRelationsFor, loadBook:loadBook, patchBook:patchBook, promoteBook:promoteBook, applyBook:applyBook, exportBookFile:exportBookFile, exportBookStFile:exportBookStFile, setBookMerge:setBookMerge };
|
|
1797
|
+
return { getSnapshot:getSnapshot, subscribe:subscribe, setCur:setCur, setPanel:setPanel, setSideExpanded:setSideExpanded, setIsCcMode:setIsCcMode, setDraftFromHost:setDraftFromHost, setSessionId:setSessionId, setSlotWarn:setSlotWarn, setAlternateSlots:setAlternateSlots, setLock:setLock, setLlmDraft:setLlmDraft, enterPreview:enterPreview, exitPreview:exitPreview, isLockActive:isLockActive, isReadOnly:isReadOnly, setDraftLock:setDraftLock, migrateSlot:migrateSlot, checkIsCcMode:checkIsCcMode, setPollEnabled:setPollEnabled, setPollInterval:setPollInterval, setAutoValidate:setAutoValidate, setAutoSaveOnImport:setAutoSaveOnImport, updateDraft:updateDraft, validate:validate, exportJSON:exportJSON, exportPNG:exportPNG, embedIntoPng:embedIntoPng, exportCharx:exportCharx, importJSON:importJSON, importAny:importAny, setWorldPatch:setWorldPatch, setEditingWorld:setEditingWorld, cancelEditingWorld:cancelEditingWorld, setEditingText:setEditingText, clearEditingText:clearEditingText, cancelEditingText:cancelEditingText, updateEditingText:updateEditingText, addEntry:addEntry, setLibraryQuery:setLibraryQuery, toggleLibraryCollapsed:toggleLibraryCollapsed, refreshLibrary:refreshLibrary, saveCurrentToLibrary:saveCurrentToLibrary, saveAsNewToLibrary:saveAsNewToLibrary, clearCurrentLibraryId:clearCurrentLibraryId, setCurrentLibraryId:setCurrentLibraryId, clearDraft:clearDraft, loadFromLibrary:loadFromLibrary, deleteFromLibrary:deleteFromLibrary, renameCurrentInLibrary:renameCurrentInLibrary, setCurrentGroupId:setCurrentGroupId, refreshGroups:refreshGroups, createGroup:createGroup, loadGroup:loadGroup, assignGroup:assignGroup, deleteGroup:deleteGroup, patchRelations:patchRelations, checkGroupNow:checkGroupNow, renderRelationsToCard:renderRelationsToCard, openRelations:openRelations, openRelationsFor:openRelationsFor, loadBook:loadBook, patchBook:patchBook, promoteBook:promoteBook, applyBook:applyBook, exportBookFile:exportBookFile, exportBookStFile:exportBookStFile, setBookMerge:setBookMerge };
|
|
1604
1798
|
}
|
|
1605
1799
|
|
|
1606
1800
|
var S={
|
|
@@ -1861,6 +2055,11 @@ window.__ModuleLoader__.load({
|
|
|
1861
2055
|
}
|
|
1862
2056
|
}, [s.panelOpen]);
|
|
1863
2057
|
if(!s.panelOpen) return null;
|
|
2058
|
+
// 只读判定:LLM 锁 active(且设置里没关保护)或正在只读预览时,所有写草稿的控件禁用。
|
|
2059
|
+
// 用 store.isReadOnly() 而不是在这里重算一遍 —— 渲染与 store 守卫必须是同一个判据,
|
|
2060
|
+
// 否则会出现「按钮亮着、点了却没反应」或者反过来(能点却被拦)。
|
|
2061
|
+
var readOnly = store.isReadOnly();
|
|
2062
|
+
var previewing = !!s.preview;
|
|
1864
2063
|
// 顶部胶囊(会话域那几个快捷入口):0.9.0 起跟左侧新结构走,取最常看的三处 ——
|
|
1865
2064
|
// 总览(整卡状态)/ 角色 / 世界书。其余入口都在左侧导航里,胶囊不追求全。
|
|
1866
2065
|
var steps=[{cur:5, key:"step.overview"}, {cur:1, key:"step.char"}, {cur:2, key:"step.lore"}];
|
|
@@ -1910,13 +2109,15 @@ window.__ModuleLoader__.load({
|
|
|
1910
2109
|
// 所以默认视图看起来就是排好版的正文,点进去直接改。长文仍可走 ⛶ 大框(带打开时快照,「取消」真回滚)。
|
|
1911
2110
|
function field(title, key, opts){
|
|
1912
2111
|
opts=opts||{};
|
|
2112
|
+
var ro = !!opts.readOnly || readOnly;
|
|
1913
2113
|
var raw = opts.value!==undefined ? opts.value : s.draft.data[key];
|
|
1914
2114
|
var val = raw==null? "" : String(raw);
|
|
1915
2115
|
var write = opts.onChange || function(v){ store.updateDraft(function(d){ d[key]=v; }); };
|
|
1916
2116
|
var tools=[];
|
|
1917
2117
|
if(opts.tools) tools=tools.concat(opts.tools);
|
|
1918
|
-
if(opts.bigBox!==false) tools.push(h("button",{key:"bigbox", type:"button", style:S.iconBtn, title:t("action.expand"), onMouseDown:stopFocusSteal,
|
|
2118
|
+
if(opts.bigBox!==false) tools.push(h("button",{key:"bigbox", type:"button", style: ro? Object.assign({}, S.iconBtn, {opacity:0.4, cursor:"default"}) : S.iconBtn, title:t("action.expand"), disabled: ro, onMouseDown:stopFocusSteal,
|
|
1919
2119
|
onClick:function(){
|
|
2120
|
+
if(ro) return; // 只读:大框也不开(大框里同样改不了,开了只会让人以为能写)
|
|
1920
2121
|
var snapshot=s.draft.data[key]; // 「取消」回滚用
|
|
1921
2122
|
store.setEditingText(title, val,
|
|
1922
2123
|
function(v){ store.updateDraft(function(d){ d[key]=v; }); },
|
|
@@ -1927,8 +2128,8 @@ window.__ModuleLoader__.load({
|
|
|
1927
2128
|
h("label",{style:S.fieldName}, title),
|
|
1928
2129
|
tools.length? h("div",{style:S.fieldTools}, tools): null),
|
|
1929
2130
|
opts.input
|
|
1930
|
-
? h("input",{style:Object.assign({}, S.readInput,
|
|
1931
|
-
: h("textarea",{style:S.readText, value:val, readOnly
|
|
2131
|
+
? h("input",{style:Object.assign({}, S.readInput, ro? {color:"var(--dsw-alias-label-secondary)"}:{}), value:val, readOnly:ro, placeholder:opts.placeholder||t("workshop.field.empty"), onChange:function(e){ write(e.target.value); }})
|
|
2132
|
+
: h("textarea",{style:S.readText, value:val, readOnly:ro, placeholder:opts.placeholder||t("workshop.field.empty"), onChange:function(e){ write(e.target.value); }})
|
|
1932
2133
|
);
|
|
1933
2134
|
}
|
|
1934
2135
|
// 每条问候语一段正文(元素内部换行原样保留,不做「每行一条」的序列化);阅读态 + 逐条 ⛶ / 删除
|
|
@@ -1940,8 +2141,8 @@ window.__ModuleLoader__.load({
|
|
|
1940
2141
|
h("label",{style:S.fieldName}, title),
|
|
1941
2142
|
h("div",{style:S.fieldTools},
|
|
1942
2143
|
h("span",{style:{fontSize:10, color:"var(--dsw-alias-label-secondary)"}}, hint+" · "+arr.length+"/"+GREETING_MAX),
|
|
1943
|
-
h("button",{type:"button", style:Object.assign({}, S.iconBtn, {borderColor:"var(--dsw-alias-border-l1)"}), disabled:atMax, onMouseDown:stopFocusSteal,
|
|
1944
|
-
onClick:function(){ store.updateDraft(function(d){ d[key]=appendGreeting(d[key]); }); }}, atMax? t("workshop.greeting.atMax",{max:GREETING_MAX}):t("workshop.greeting.add")))
|
|
2144
|
+
h("button",{type:"button", style:Object.assign({}, S.iconBtn, {borderColor:"var(--dsw-alias-border-l1)"}), disabled:atMax||readOnly, onMouseDown:stopFocusSteal,
|
|
2145
|
+
onClick:function(){ if(readOnly) return; store.updateDraft(function(d){ d[key]=appendGreeting(d[key]); }); }}, atMax? t("workshop.greeting.atMax",{max:GREETING_MAX}):t("workshop.greeting.add")))
|
|
1945
2146
|
),
|
|
1946
2147
|
arr.length===0
|
|
1947
2148
|
? h("div",{style:{fontSize:12, color:"var(--dsw-alias-label-secondary)", border:"1px dashed var(--dsw-alias-border-l1)", borderRadius:10, padding:"12px", textAlign:"center"}}, t("workshop.greeting.empty"))
|
|
@@ -1951,16 +2152,17 @@ window.__ModuleLoader__.load({
|
|
|
1951
2152
|
h("div",{style:S.fieldLabel},
|
|
1952
2153
|
h("span",{style:{fontSize:11, fontWeight:600, color:"var(--dsw-alias-label-secondary)"}}, label),
|
|
1953
2154
|
h("div",{style:S.fieldTools},
|
|
1954
|
-
h("button",{type:"button", style:S.iconBtn, title:t("action.expand"), onMouseDown:stopFocusSteal,
|
|
2155
|
+
h("button",{type:"button", style: readOnly? Object.assign({}, S.iconBtn, {opacity:0.4, cursor:"default"}) : S.iconBtn, title:t("action.expand"), disabled:readOnly, onMouseDown:stopFocusSteal,
|
|
1955
2156
|
onClick:function(){
|
|
2157
|
+
if(readOnly) return;
|
|
1956
2158
|
store.setEditingText(label, String(text==null? "": text),
|
|
1957
2159
|
function(v){ store.updateDraft(function(d){ d[key]=setGreetingAt(d[key], i, v); }); },
|
|
1958
2160
|
function(){ store.updateDraft(function(d){ d[key]=setGreetingAt(d[key], i, text); }); });
|
|
1959
2161
|
}}, "⛶"),
|
|
1960
|
-
h("button",{type:"button", style:Object.assign({}, S.iconBtn, {color:"#e8a6a6"}), onMouseDown:stopFocusSteal,
|
|
1961
|
-
onClick:function(){ store.updateDraft(function(d){ d[key]=removeGreetingAt(d[key], i); }); }}, t("common.delete")))
|
|
2162
|
+
h("button",{type:"button", style:Object.assign({}, S.iconBtn, {color:"#e8a6a6"}), disabled:readOnly, onMouseDown:stopFocusSteal,
|
|
2163
|
+
onClick:function(){ if(readOnly) return; store.updateDraft(function(d){ d[key]=removeGreetingAt(d[key], i); }); }}, t("common.delete")))
|
|
1962
2164
|
),
|
|
1963
|
-
h("textarea",{style:S.readText, value:String(text==null? "": text), placeholder:t("workshop.field.empty"),
|
|
2165
|
+
h("textarea",{style:S.readText, value:String(text==null? "": text), readOnly:readOnly, placeholder:t("workshop.field.empty"),
|
|
1964
2166
|
onChange:function(e){ var v=e.target.value; store.updateDraft(function(d){ d[key]=setGreetingAt(d[key], i, v); }); }}));
|
|
1965
2167
|
})
|
|
1966
2168
|
);
|
|
@@ -2000,8 +2202,8 @@ window.__ModuleLoader__.load({
|
|
|
2000
2202
|
h("label",{style:S.fieldName}, title),
|
|
2001
2203
|
h("div",{style:S.fieldTools},
|
|
2002
2204
|
h("span",{style:{fontSize:10, color:"var(--dsw-alias-label-secondary)"}}, t("workshop.world.chars",{n:String(val.length)})),
|
|
2003
|
-
h("button",{type:"button", style:S.iconBtn, title:t("action.expand"), onMouseDown:stopFocusSteal, onClick:function(){ store.setEditingWorld(key); }}, "⛶"))),
|
|
2004
|
-
h("textarea",{style:S.readText, value:String(val), placeholder:t("workshop.world.empty"),
|
|
2205
|
+
h("button",{type:"button", style: readOnly? Object.assign({}, S.iconBtn, {opacity:0.4, cursor:"default"}) : S.iconBtn, title:t("action.expand"), disabled:readOnly, onMouseDown:stopFocusSteal, onClick:function(){ if(!readOnly) store.setEditingWorld(key); }}, "⛶"))),
|
|
2206
|
+
h("textarea",{style:S.readText, value:String(val), readOnly:readOnly, placeholder:t("workshop.world.empty"),
|
|
2005
2207
|
onChange:function(e){ store.setWorldPatch(key, e.target.value); }}));
|
|
2006
2208
|
}
|
|
2007
2209
|
main=h("div",null,
|
|
@@ -2067,7 +2269,7 @@ window.__ModuleLoader__.load({
|
|
|
2067
2269
|
h("div",{style:{display:"flex", gap:8, flexWrap:"wrap"}},
|
|
2068
2270
|
h("button",{type:"button", style:S.btn, onMouseDown:stopFocusSteal, onClick:function(){ store.validate(); }}, s.busy==="validate"? t("action.validating") : t("action.validate")),
|
|
2069
2271
|
h("button",{type:"button", style:Object.assign({}, S.btn, S.btnGhost), onMouseDown:stopFocusSteal, onClick:function(){ store.setCur(3); }}, t("step.export")),
|
|
2070
|
-
h("button",{type:"button", style:Object.assign({}, S.btn, S.btnGhost), onMouseDown:stopFocusSteal, onClick:function(){ store.saveCurrentToLibrary(); }}, t("workshop.library.save"))),
|
|
2272
|
+
h("button",{type:"button", style:Object.assign({}, S.btn, S.btnGhost), disabled:previewing, onMouseDown:stopFocusSteal, onClick:function(){ store.saveCurrentToLibrary(); }}, t("workshop.library.save"))),
|
|
2071
2273
|
s.lastMsg? h("div",{style:{marginTop:10, fontSize:12, color:"var(--dsw-alias-label-secondary)", lineHeight:1.7}}, s.lastMsg):null)
|
|
2072
2274
|
);
|
|
2073
2275
|
} else if(s.cur===7){
|
|
@@ -2199,11 +2401,11 @@ window.__ModuleLoader__.load({
|
|
|
2199
2401
|
h("div",{style:{fontSize:13, lineHeight:1.8, color:"var(--dsw-alias-label-primary)", marginTop:4, whiteSpace:"pre-wrap", wordBreak:"break-word"}}, e.content),
|
|
2200
2402
|
h("div",{style:{display:"flex", flexWrap:"wrap", gap:6, marginTop:6}}, h("span",{style:S.badge},"@@depth/@@position"), h("span",{style:S.badge},"CBS: {{char}}"))
|
|
2201
2403
|
),
|
|
2202
|
-
h("div",{style:{display:"flex", flexDirection:"column", gap:6}}, h("label",{style:{fontSize:12}}, h("input",{type:"checkbox", checked:e.enabled, onChange:function(ev){ store.updateDraft(function(d){ var f=d.character_book.entries.find(function(x){return x.id===e.id;}); if(f) f.enabled=ev.target.checked; }); }}), " enabled"), h("button",{style: Object.assign({}, S.btn, S.btnGhost, {padding:"4px 8px", fontSize:12}), onClick:function(){ store.updateDraft(function(d){ d.character_book.entries=d.character_book.entries.filter(function(x){return x.id!==e.id;}); }); }}, t("common.delete")))
|
|
2404
|
+
h("div",{style:{display:"flex", flexDirection:"column", gap:6}}, h("label",{style:{fontSize:12}}, h("input",{type:"checkbox", checked:e.enabled, disabled:readOnly, onChange:function(ev){ store.updateDraft(function(d){ var f=d.character_book.entries.find(function(x){return x.id===e.id;}); if(f) f.enabled=ev.target.checked; }); }}), " enabled"), h("button",{style: Object.assign({}, S.btn, S.btnGhost, {padding:"4px 8px", fontSize:12}), disabled:readOnly, onClick:function(){ store.updateDraft(function(d){ d.character_book.entries=d.character_book.entries.filter(function(x){return x.id!==e.id;}); }); }}, t("common.delete")))
|
|
2203
2405
|
);
|
|
2204
2406
|
})),
|
|
2205
2407
|
h("div",{style:{display:"flex", gap:8, marginTop:10}},
|
|
2206
|
-
h("button",{style:S.btn, onClick:function(){ store.addEntry(); }}, t("action.addEntry")),
|
|
2408
|
+
h("button",{style:S.btn, disabled:readOnly, onClick:function(){ store.addEntry(); }}, t("action.addEntry")),
|
|
2207
2409
|
h("span",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", alignSelf:"center"}}, t("workshop.lore.addHint"))
|
|
2208
2410
|
),
|
|
2209
2411
|
s.lastMsg? h("div",{style:{marginTop:8, fontSize:12, color:"var(--dsw-alias-label-secondary)"}}, s.lastMsg):null
|
|
@@ -2225,10 +2427,11 @@ window.__ModuleLoader__.load({
|
|
|
2225
2427
|
v.errors.length? h("div",{style:{background:"color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, var(--dsw-alias-bg-base))", border:"1px solid var(--dsw-alias-state-error-primary)", borderRadius:8, padding:8, marginBottom:8, color:"#e8a6a6", fontSize:12}}, v.errors.map(function(e){ return h("div",{key:e}, "• "+e); })):null,
|
|
2226
2428
|
v.warnings.length? h("div",{style:{background:"color-mix(in srgb, var(--dsw-alias-border-l1) 40%, var(--dsw-alias-bg-base))", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:8, padding:8, marginBottom:8, color:"var(--dsw-alias-label-secondary)", fontSize:12}}, v.warnings.map(function(e){ return h("div",{key:e}, "• "+e); })):null,
|
|
2227
2429
|
h("div",{style:{display:"flex", flexWrap:"wrap", gap:8, margin:"12px 0"}},
|
|
2228
|
-
|
|
2229
|
-
h("button",{style:
|
|
2230
|
-
h("button",{style: Object.assign({}, S.btn, {background:"#e05555",
|
|
2231
|
-
h("button",{style: Object.assign({}, S.btn, {background:"#
|
|
2430
|
+
// 预览时它们导出的是「别人卡的一份影像」,不是工坊里的卡 —— 全部禁用(锁 active 但未预览时允许导出)
|
|
2431
|
+
h("button",{style:S.btn, disabled:previewing, onClick:function(){ store.exportJSON(); }}, t("action.exportJson")),
|
|
2432
|
+
h("button",{style: Object.assign({}, S.btn, {background:"#e05555"}), disabled:previewing, onClick:function(){ store.exportPNG(); }}, s.busy==="exportPng"? t("action.generating"):t("action.exportPng")),
|
|
2433
|
+
h("button",{style: Object.assign({}, S.btn, {background:"#e05555", opacity:0.92}), disabled:previewing, onClick:function(){ store.embedIntoPng(); }}, s.busy==="exportPng"? t("action.writing"):t("action.embedPng")),
|
|
2434
|
+
h("button",{style: Object.assign({}, S.btn, {background:"#2e86de"}), disabled:previewing, onClick:function(){ store.exportCharx(); }}, s.busy==="exportCharx"? t("action.generating"):t("action.exportCharx")),
|
|
2232
2435
|
h("button",{style: Object.assign({}, S.btn, S.btnGhost), onClick:function(){ store.validate(); }}, s.busy==="validate"? t("action.validating"): t("action.validate")),
|
|
2233
2436
|
h("span",{style:{color:"var(--dsw-alias-label-secondary)", fontSize:11, alignSelf:"center"}}, t("workshop.export.hint2"))
|
|
2234
2437
|
),
|
|
@@ -2385,7 +2588,7 @@ window.__ModuleLoader__.load({
|
|
|
2385
2588
|
onClick:function(){ setRelEdit({ edgeId:e.id, from:e.from, to:e.to, type:e.type, strength:e.strength, direction:e.direction, note:e.note||"" }); }}, t("workshop.rel.edit")));
|
|
2386
2589
|
})),
|
|
2387
2590
|
h("div",{style:{display:"flex", gap:8, marginTop:14, flexWrap:"wrap"}},
|
|
2388
|
-
h("button",{type:"button", style:Object.assign({}, S.btn, {padding:"4px 12px", fontSize:12}), onClick:function(){ store.renderRelationsToCard(); }, disabled: s.relBusy==="render"}, s.relBusy==="render" ? "…" : t("workshop.rel.render")),
|
|
2591
|
+
h("button",{type:"button", style:Object.assign({}, S.btn, {padding:"4px 12px", fontSize:12}), onClick:function(){ store.renderRelationsToCard(); }, disabled: readOnly || s.relBusy==="render"}, s.relBusy==="render" ? "…" : t("workshop.rel.render")),
|
|
2389
2592
|
h("button",{type:"button", style:Object.assign({}, S.btnGhost, {border:"1px solid var(--dsw-alias-border-l1)", background:"transparent", color:"var(--dsw-alias-label-primary)", borderRadius:8, padding:"4px 12px", fontSize:12}), onClick:function(){ store.checkGroupNow(); }, disabled: s.relBusy==="check"}, s.relBusy==="check" ? "…" : t("workshop.rel.check")),
|
|
2390
2593
|
h("button",{type:"button", style:Object.assign({}, S.btnGhost, {border:"1px solid var(--dsw-alias-border-l1)", background:"transparent", color:"var(--dsw-alias-label-secondary)", borderRadius:8, padding:"4px 12px", fontSize:12}), onClick:function(){ store.loadGroup(gid); }}, t("action.refresh")),
|
|
2391
2594
|
h("button",{type:"button", style:Object.assign({}, S.btnGhost, {border:"1px solid #3a2a2a", background:"transparent", color:"#e8a6a6", borderRadius:8, padding:"4px 12px", fontSize:12}), onClick:function(){ store.deleteGroup(gid); }}, t("common.delete")))),
|
|
@@ -2519,7 +2722,7 @@ window.__ModuleLoader__.load({
|
|
|
2519
2722
|
return h("div",{style:S.libraryCollapsedBar},
|
|
2520
2723
|
h("button",{style:Object.assign({}, S.btnGhost, {border:"1px solid var(--dsw-alias-border-l1)", background:"transparent", color:"var(--dsw-alias-label-primary)", borderRadius:8, padding:"6px 8px", fontSize:12, cursor:"pointer", width:28, height:28, display:"flex", alignItems:"center", justifyContent:"center"}), onClick:function(){ store.toggleLibraryCollapsed(); }, title:t("workshop.library.expand")}, "◀"),
|
|
2521
2724
|
h("div",{style:{writingMode:"vertical-rl", fontSize:12, color:"var(--dsw-alias-label-secondary)", letterSpacing:2, lineHeight:"16px"}}, t("workshop.library.collapsed").replace("{n}", String(s.library.length))),
|
|
2522
|
-
h("button",{style:Object.assign({}, S.btn, {padding:"6px 0", fontSize:14, width:28, height:28, display:"flex", alignItems:"center", justifyContent:"center"}), onClick:function(){ store.saveCurrentToLibrary(); }, disabled: s.libraryBusy==="save", title:t("workshop.library.saveTip",{name:(s.draft.data.name||t("workshop.library.unnamed"))})}, s.libraryBusy==="save"?"…":"★"),
|
|
2725
|
+
h("button",{style:Object.assign({}, S.btn, {padding:"6px 0", fontSize:14, width:28, height:28, display:"flex", alignItems:"center", justifyContent:"center"}), onClick:function(){ store.saveCurrentToLibrary(); }, disabled: s.libraryBusy==="save"||previewing, title:t("workshop.library.saveTip",{name:(s.draft.data.name||t("workshop.library.unnamed"))})}, s.libraryBusy==="save"?"…":"★"),
|
|
2523
2726
|
h("span",{style:{fontSize:10, color:"var(--dsw-alias-label-secondary)"}}, filteredLibrary.length+"/"+s.library.length)
|
|
2524
2727
|
);
|
|
2525
2728
|
}
|
|
@@ -2543,12 +2746,12 @@ window.__ModuleLoader__.load({
|
|
|
2543
2746
|
h("div",{style:{fontSize:10, color:"var(--dsw-alias-label-secondary)", textAlign:"center"}}, t("workshop.library.importHint"))
|
|
2544
2747
|
),
|
|
2545
2748
|
h("div",{style:{display:"flex", gap:6}},
|
|
2546
|
-
h("button",{style:Object.assign({}, S.btn, {flex:1, padding:"6px 10px", fontSize:12}), onClick:function(){ store.saveCurrentToLibrary(); }, disabled: s.libraryBusy==="save"||s.libraryBusy==="saveNew"}, s.libraryBusy==="save"? t("store.saving"):(s.currentLibraryId? t("workshop.library.update"):t("workshop.library.save"))),
|
|
2547
|
-
h("button",{style:Object.assign({}, S.btnGhost, {flex:1, padding:"6px 10px", fontSize:12, border:"1px solid var(--dsw-alias-border-l1)", background:"transparent", color:"var(--dsw-alias-label-primary)"}), onClick:function(){ store.saveAsNewToLibrary(); }, disabled: s.libraryBusy==="save"||s.libraryBusy==="saveNew"}, s.libraryBusy==="saveNew"? t("store.savingNew"):t("workshop.library.saveAs"))
|
|
2749
|
+
h("button",{style:Object.assign({}, S.btn, {flex:1, padding:"6px 10px", fontSize:12}), onClick:function(){ store.saveCurrentToLibrary(); }, disabled: s.libraryBusy==="save"||s.libraryBusy==="saveNew"||previewing}, s.libraryBusy==="save"? t("store.saving"):(s.currentLibraryId? t("workshop.library.update"):t("workshop.library.save"))),
|
|
2750
|
+
h("button",{style:Object.assign({}, S.btnGhost, {flex:1, padding:"6px 10px", fontSize:12, border:"1px solid var(--dsw-alias-border-l1)", background:"transparent", color:"var(--dsw-alias-label-primary)"}), onClick:function(){ store.saveAsNewToLibrary(); }, disabled: s.libraryBusy==="save"||s.libraryBusy==="saveNew"||previewing}, s.libraryBusy==="saveNew"? t("store.savingNew"):t("workshop.library.saveAs"))
|
|
2548
2751
|
),
|
|
2549
2752
|
h("div",{style:{display:"flex", gap:6}},
|
|
2550
|
-
h("button",{style:Object.assign({}, S.btnGhost, {flex:1, padding:"4px 6px", fontSize:11, border:"1px solid var(--dsw-alias-border-l1)", background:"transparent", color:"var(--dsw-alias-label-secondary)"}), onClick:function(){ store.renameCurrentInLibrary(); }, disabled: !s.currentLibraryId||!!s.libraryBusy}, t("workshop.library.rename")),
|
|
2551
|
-
h("button",{style:Object.assign({}, S.btnGhost, {flex:1, padding:"4px 6px", fontSize:11, border:"1px solid var(--dsw-alias-border-l1)", background:"transparent", color:"var(--dsw-alias-label-secondary)"}), onClick:function(){ store.clearCurrentLibraryId(); }}, t("workshop.library.new")),
|
|
2753
|
+
h("button",{style:Object.assign({}, S.btnGhost, {flex:1, padding:"4px 6px", fontSize:11, border:"1px solid var(--dsw-alias-border-l1)", background:"transparent", color:"var(--dsw-alias-label-secondary)"}), onClick:function(){ store.renameCurrentInLibrary(); }, disabled: !s.currentLibraryId||!!s.libraryBusy||previewing}, t("workshop.library.rename")),
|
|
2754
|
+
h("button",{style:Object.assign({}, S.btnGhost, {flex:1, padding:"4px 6px", fontSize:11, border:"1px solid var(--dsw-alias-border-l1)", background:"transparent", color:"var(--dsw-alias-label-secondary)"}), onClick:function(){ store.clearCurrentLibraryId(); }, disabled: readOnly}, t("workshop.library.new")),
|
|
2552
2755
|
h("span",{style:{fontSize:10, color:"var(--dsw-alias-label-secondary)", alignSelf:"center", overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap", flex:1, textAlign:"right"}}, s.draft.data.name ? s.draft.data.name.slice(0,8) : t("workshop.library.unnamed"))
|
|
2553
2756
|
),
|
|
2554
2757
|
h("input",{placeholder:t("workshop.library.searchPlaceholder"), style:{width:"100%", boxSizing:"border-box", maxWidth:"100%", background:"var(--dsw-alias-bg-base)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:8, color:"var(--dsw-alias-label-primary)", padding:"6px 8px", fontSize:12}, value:s.libraryQuery, onChange:function(e){ store.setLibraryQuery(e.target.value); }}),
|
|
@@ -2588,6 +2791,28 @@ window.__ModuleLoader__.load({
|
|
|
2588
2791
|
),
|
|
2589
2792
|
(function(){
|
|
2590
2793
|
var st = s.draftStatus;
|
|
2794
|
+
// —— 0.11.0 草稿锁横幅(预览优先显示)——
|
|
2795
|
+
// 预览:用户在看「假如载入会变成什么」。它必须在最前面说清楚,否则用户会以为工坊里就是这张卡。
|
|
2796
|
+
if(s.preview){
|
|
2797
|
+
return h("div",{style:{margin:"10px 16px 0", padding:"8px 12px", borderRadius:10, fontSize:12, lineHeight:1.5, background:"rgba(96,165,250,.10)", border:"1px solid rgba(96,165,250,.45)", color:"var(--dsw-alias-label-primary)", flex:"none"}},
|
|
2798
|
+
h("div",{style:{display:"flex", alignItems:"center", gap:8, flexWrap:"wrap"}},
|
|
2799
|
+
h("span",null, t("lock.bar.preview",{name:String(s.preview.name||t("workshop.library.unnamed"))})),
|
|
2800
|
+
h("button",{type:"button", style:Object.assign({}, S.btn, {padding:"2px 10px", fontSize:11}), onMouseDown:stopFocusSteal, onClick:function(){ store.exitPreview(); }}, t("lock.preview.exit"))),
|
|
2801
|
+
h("div",{style:{fontSize:11, marginTop:4, lineHeight:1.6, color:"var(--dsw-alias-label-secondary)"}}, t("lock.bar.previewHint")));
|
|
2802
|
+
}
|
|
2803
|
+
// 锁 active:工坊只读。分钟数用宿主算好的 ageSec 换算 —— 客户端不重算时间,免得与宿主时钟打架。
|
|
2804
|
+
var lk = s.lock;
|
|
2805
|
+
if(s.draftLock && lk && lk.owner==="llm"){
|
|
2806
|
+
var mins = Math.max(1, Math.round((Number(lk.ageSec)||0)/60));
|
|
2807
|
+
if(lk.stale){
|
|
2808
|
+
// 超时 = 自动解锁(安全阀):只提示,不置只读 —— LLM 再写会自动重新上锁。
|
|
2809
|
+
return h("div",{style:{margin:"10px 16px 0", padding:"8px 12px", borderRadius:10, fontSize:12, lineHeight:1.5, background:"rgba(127,127,127,.10)", border:"1px solid var(--dsw-alias-border-l1)", color:"var(--dsw-alias-label-secondary)", flex:"none"}},
|
|
2810
|
+
t("lock.bar.stale",{n:String(mins)}));
|
|
2811
|
+
}
|
|
2812
|
+
return h("div",{style:{margin:"10px 16px 0", padding:"8px 12px", borderRadius:10, fontSize:12, lineHeight:1.5, background:"rgba(255,170,60,.10)", border:"1px solid rgba(255,170,60,.45)", color:"var(--dsw-alias-label-primary)", flex:"none"}},
|
|
2813
|
+
h("div",null, t("lock.bar.active",{name:(String(lk.cardName||"").trim()||t("workshop.library.unnamed")), n:String(mins)})),
|
|
2814
|
+
h("div",{style:{fontSize:11, marginTop:2, lineHeight:1.6, color:"var(--dsw-alias-label-secondary)"}}, t("lock.bar.activeHint")));
|
|
2815
|
+
}
|
|
2591
2816
|
// 草稿槽不一致 / 发现别的槽有内容时的提示(issue #5):把「不一致」变成看得见的东西,
|
|
2592
2817
|
// 并给一个真的能把内容捞回来的按钮 —— 用户看到空壳时的第一反应是「草稿丢了」。
|
|
2593
2818
|
if(s.slotWarn){
|
|
@@ -2600,7 +2825,7 @@ window.__ModuleLoader__.load({
|
|
|
2600
2825
|
h("div",{style:{marginTop:8, display:"flex", gap:8, flexWrap:"wrap"}},
|
|
2601
2826
|
s.alternateSlots.map(function(slot){
|
|
2602
2827
|
return h("button",{key:String(slot.key), style:Object.assign({}, S.btn, {padding:"4px 10px", fontSize:11}),
|
|
2603
|
-
disabled: s.busy==="migrate",
|
|
2828
|
+
disabled: readOnly || s.busy==="migrate",
|
|
2604
2829
|
onClick:function(){ store.migrateSlot(String(slot.key)); }},
|
|
2605
2830
|
t("workshop.slot.migrateOne",{key:String(slot.key), n:String(slot.entries||0)}));
|
|
2606
2831
|
})
|
|
@@ -2655,8 +2880,9 @@ window.__ModuleLoader__.load({
|
|
|
2655
2880
|
onClick:function(){ store.setCur(3); }}, t("step.export")),
|
|
2656
2881
|
// 清空是破坏性操作:先过二次确认才动手;工坊里没有撤销,所以按钮用危险色并与「校验」分开放
|
|
2657
2882
|
h("button",{type:"button", style: Object.assign({}, S.btn, S.btnGhost, {padding:"6px 10px", fontSize:12, borderColor:"rgba(224,85,85,.55)", color:"#e05555"}),
|
|
2883
|
+
disabled: readOnly,
|
|
2658
2884
|
onMouseDown:function(e){ if(e && e.preventDefault) e.preventDefault(); },
|
|
2659
|
-
onClick:function(){ if(confirmClear(t)) store.clearDraft(); }}, t("action.clear")),
|
|
2885
|
+
onClick:function(){ if(!readOnly && confirmClear(t)) store.clearDraft(); }}, t("action.clear")),
|
|
2660
2886
|
h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", textAlign:"center"}}, s.busy? s.busy: (s.lastMsg||t("workshop.library.noCcHint")))
|
|
2661
2887
|
)
|
|
2662
2888
|
),
|
|
@@ -2682,7 +2908,7 @@ window.__ModuleLoader__.load({
|
|
|
2682
2908
|
h("strong",{style:{fontSize:14, color:"var(--dsw-alias-label-primary)"}}, t("workshop.edit.title",{title:({timeline:t("workshop.world.timeline"), factions:t("workshop.world.factions"), geo:t("workshop.world.geo"), power:t("workshop.world.power"), daily:t("workshop.world.daily")}[s.editingWorld] || s.editingWorld)})),
|
|
2683
2909
|
h("button",{style:Object.assign({}, S.btnGhost, {padding:"4px 10px"}), onClick:function(){ store.setEditingWorld(null); }}, t("action.closeX"))
|
|
2684
2910
|
),
|
|
2685
|
-
h("textarea",{style:Object.assign({}, S.textarea, {minHeight:320, flex:1, maxHeight:"60vh"}), value: s.world[s.editingWorld]||"", onChange:function(e){ store.setWorldPatch(s.editingWorld, e.target.value); }, autoFocus:true, placeholder:t("workshop.world.placeholder")}),
|
|
2911
|
+
h("textarea",{style:Object.assign({}, S.textarea, {minHeight:320, flex:1, maxHeight:"60vh"}), value: s.world[s.editingWorld]||"", readOnly: readOnly, onChange:function(e){ store.setWorldPatch(s.editingWorld, e.target.value); }, autoFocus:true, placeholder:t("workshop.world.placeholder")}),
|
|
2686
2912
|
h("div",{style:{display:"flex", gap:8, marginTop:12, justifyContent:"space-between", alignItems:"center"}},
|
|
2687
2913
|
h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)"}}, t("workshop.world.charsHint",{n:String((s.world[s.editingWorld]||"").length)})),
|
|
2688
2914
|
h("div",{style:{display:"flex", gap:8}},
|
|
@@ -2698,7 +2924,7 @@ window.__ModuleLoader__.load({
|
|
|
2698
2924
|
h("strong",{style:{fontSize:14, color:"var(--dsw-alias-label-primary)"}}, t("workshop.edit.title",{title:(s.editingText.title||t("workshop.edit.fallbackTitle"))})),
|
|
2699
2925
|
h("button",{style:Object.assign({}, S.btnGhost, {padding:"4px 10px"}), onClick:function(){ store.clearEditingText(); }}, t("action.closeX"))
|
|
2700
2926
|
),
|
|
2701
|
-
h("textarea",{style:Object.assign({}, S.textarea, {minHeight:320, flex:1, maxHeight:"60vh"}), value: s.editingText.value||"", onChange:function(e){ store.updateEditingText(e.target.value); }, autoFocus:true, placeholder:t("workshop.edit.placeholder")}),
|
|
2927
|
+
h("textarea",{style:Object.assign({}, S.textarea, {minHeight:320, flex:1, maxHeight:"60vh"}), value: s.editingText.value||"", readOnly: readOnly, onChange:function(e){ store.updateEditingText(e.target.value); }, autoFocus:true, placeholder:t("workshop.edit.placeholder")}),
|
|
2702
2928
|
h("div",{style:{display:"flex", gap:8, marginTop:12, justifyContent:"space-between", alignItems:"center"}},
|
|
2703
2929
|
h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)"}}, t("workshop.edit.charsSync",{n:String((s.editingText.value||"").length)})),
|
|
2704
2930
|
h("div",{style:{display:"flex", gap:8}},
|
|
@@ -2727,6 +2953,7 @@ window.__ModuleLoader__.load({
|
|
|
2727
2953
|
var pollEnabled = snap ? !!snap.pollEnabled : true;
|
|
2728
2954
|
var autoValidate = snap ? !!snap.autoValidate : true;
|
|
2729
2955
|
var autoSaveOnImport = snap ? !!snap.autoSaveOnImport : false;
|
|
2956
|
+
var draftLock = snap ? snap.draftLock!==false : true;
|
|
2730
2957
|
function ToggleRow(label, desc, value, setter){
|
|
2731
2958
|
return h("div",{style:{display:"flex", alignItems:"center", justifyContent:"space-between", padding:"8px 0", borderTop:"1px solid var(--dsw-alias-border-l1)", gap:12}},
|
|
2732
2959
|
h("div",{style:{flex:1, minWidth:0}},
|
|
@@ -2774,6 +3001,8 @@ window.__ModuleLoader__.load({
|
|
|
2774
3001
|
})(),
|
|
2775
3002
|
ToggleRow(t("settings.behavior.autoValidate"), t("settings.behavior.autoValidateDesc"), autoValidate, function(v){ store.setAutoValidate(v); }),
|
|
2776
3003
|
ToggleRow(t("settings.behavior.autoSave"), t("settings.behavior.autoSaveDesc"), autoSaveOnImport, function(v){ store.setAutoSaveOnImport(v); }),
|
|
3004
|
+
// 0.11.0:默认开。关掉 = 客户端所有写 RPC 带 force:true、锁不再置只读 —— 完全旧行为。
|
|
3005
|
+
ToggleRow(t("settings.behavior.draftLock"), t("settings.behavior.draftLockDesc"), draftLock, function(v){ store.setDraftLock(v); }),
|
|
2777
3006
|
h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", marginTop:8}}, t("settings.behavior.storageHint"))
|
|
2778
3007
|
),
|
|
2779
3008
|
h("div",{style:{marginTop:12, background:"var(--dsw-alias-bg-layer-1)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:12, padding:12}},
|
|
@@ -2819,13 +3048,22 @@ window.__ModuleLoader__.load({
|
|
|
2819
3048
|
try{ if(localStorage.getItem("dsh-cc-studio-legacy-notice")==="1") alts = null; }catch(e){}
|
|
2820
3049
|
}
|
|
2821
3050
|
store.setAlternateSlots(alts);
|
|
3051
|
+
// 锁状态每次轮询都刷新(宿主把它放在 draftStatus.lock;无锁 = null)。
|
|
3052
|
+
// 预览期间也要刷:LLM 释放后横幅要能变回「可编辑」,但显示的那张预览卡不能因此被换掉。
|
|
3053
|
+
store.setLock((res.value.draftStatus && res.value.draftStatus.lock) ? res.value.draftStatus.lock : null);
|
|
3054
|
+
var draft=res.value.draft;
|
|
3055
|
+
var draftStatus=res.value.draftStatus||null;
|
|
3056
|
+
// 预览中:宿主这份草稿是 LLM 的,只更新快照(退出预览时恢复),绝不画到当前草稿上 ——
|
|
3057
|
+
// 否则每 4s 轮询就会把用户正在看的预览卡覆盖回 LLM 的卡,预览等于「自己退出」。
|
|
3058
|
+
if(store.getSnapshot().preview){
|
|
3059
|
+
store.setLlmDraft(draft, draftStatus);
|
|
3060
|
+
return;
|
|
3061
|
+
}
|
|
2822
3062
|
// 载入来源 id(0.5.0):宿主把「这份草稿是从哪张已存卡载入的」落在盘上(sidecar),
|
|
2823
3063
|
// 刷新页面后据此恢复「已载入 ID」—— 否则内存里的 currentLibraryId 一丢,
|
|
2824
3064
|
// 再点保存就会新建一张同名重复卡(LLM 侧回环测试抓到的 P0-4)。
|
|
2825
3065
|
var originId = (typeof res.value.originId==="string" && res.value.originId) ? res.value.originId : null;
|
|
2826
|
-
if(originId !== store.getSnapshot().currentLibraryId)
|
|
2827
|
-
var draft=res.value.draft;
|
|
2828
|
-
var draftStatus=res.value.draftStatus||null;
|
|
3066
|
+
if(originId !== store.getSnapshot().currentLibraryId) store.setCurrentLibraryId(originId);
|
|
2829
3067
|
try{
|
|
2830
3068
|
var j=JSON.stringify(draft);
|
|
2831
3069
|
var stJ=JSON.stringify(draftStatus);
|