@xia-sc/dsh-cc-studio 0.8.1 → 0.8.3
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 +28 -25
- package/lib/agent.js +757 -6
- package/lib/client.js +641 -129
- package/lib/index.js +423 -29
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -11,6 +11,20 @@ window.__ModuleLoader__.load({
|
|
|
11
11
|
var React=require("react");
|
|
12
12
|
var name="dsh-cc-studio";
|
|
13
13
|
var inject=["slots","connection","locale"];
|
|
14
|
+
// 原生 <select> 展开后的 option 列表不继承页面配色(深色模式下呈白底浅字、读不出来)。
|
|
15
|
+
// option 无法用 inline style 逐个维护,这里补一条跟随主题变量的全局规则。
|
|
16
|
+
(function(){
|
|
17
|
+
try{
|
|
18
|
+
var st=document.createElement("style");
|
|
19
|
+
st.setAttribute("data-dsh-cc-studio","select-theme");
|
|
20
|
+
st.textContent=[
|
|
21
|
+
"select option{background-color:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary)}",
|
|
22
|
+
"select option:checked{background-color:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font-weight:600}",
|
|
23
|
+
"select option:disabled{color:var(--dsw-alias-label-secondary)}"
|
|
24
|
+
].join("");
|
|
25
|
+
(document.head||document.documentElement).appendChild(st);
|
|
26
|
+
}catch(e){}
|
|
27
|
+
})();
|
|
14
28
|
var NS="dshCcStudio";
|
|
15
29
|
var zh={
|
|
16
30
|
"capsule.open":"🎭 角色卡工坊",
|
|
@@ -23,13 +37,65 @@ window.__ModuleLoader__.load({
|
|
|
23
37
|
"panel.title":"角色卡工坊",
|
|
24
38
|
"panel.subtitle":"CCv3 · 从点子到可导入卡",
|
|
25
39
|
"panel.badge":"dsh-cc-studio · 融合",
|
|
26
|
-
"step.world":"
|
|
27
|
-
"step.char":"
|
|
40
|
+
"step.world":"世界观",
|
|
41
|
+
"step.char":"角色",
|
|
28
42
|
"step.lore":"世界书",
|
|
29
43
|
"step.export":"校验导出",
|
|
44
|
+
"step.overview":"总览",
|
|
45
|
+
"step.greetings":"开场与示例",
|
|
46
|
+
"step.rel":"关系与分组",
|
|
47
|
+
"step.book":"共享世界书",
|
|
30
48
|
"step.world.desc":"年表/势力/地理/力量/日常",
|
|
31
|
-
"step.char.desc":"六件套
|
|
32
|
-
"step.lore.desc":"
|
|
49
|
+
"step.char.desc":"六件套 · 标签 · 作者",
|
|
50
|
+
"step.lore.desc":"条目 · 预算 · 触发词",
|
|
51
|
+
"step.overview.desc":"进度 · 统计 · 快捷动作",
|
|
52
|
+
"step.greetings.desc":"首条 · 备选 · 群聊 · 示例",
|
|
53
|
+
"step.rel.desc":"图谱 · 成员 · 渲染",
|
|
54
|
+
"step.book.desc":"一组一份 · 去重",
|
|
55
|
+
// —— 0.9.0 共享世界书(组级)——
|
|
56
|
+
"workshop.rel.edit":"编辑",
|
|
57
|
+
"workshop.rel.editTitle":"编辑关系",
|
|
58
|
+
"workshop.rel.addTitle":"添加关系",
|
|
59
|
+
"workshop.rel.typeLabel":"类型",
|
|
60
|
+
"workshop.rel.dirLabel":"方向",
|
|
61
|
+
"workshop.rel.myCard":"当前卡",
|
|
62
|
+
"book.title":"共享世界书",
|
|
63
|
+
"book.hint":"一组一份:世界设定(年表/势力/地理/力量/日常)放这里,成员卡里只留角色私有条目。导出后是 CCv3 官方的 lorebook_v3 JSON —— 在聊天客户端里绑成全局世界书,多张卡共用一本,卡里就不必再重复存。",
|
|
64
|
+
"book.noGroup":"共享世界书是按分组存放的:先在右侧「已存角色」里建一个分组,并把成员卡拉进去。",
|
|
65
|
+
"book.empty":"这个分组还没有共享世界书。",
|
|
66
|
+
"book.name":"书名",
|
|
67
|
+
"book.desc":"备注",
|
|
68
|
+
"book.depth":"扫描深度",
|
|
69
|
+
"book.budget":"token 预算",
|
|
70
|
+
"book.tokens":"约 {n} token",
|
|
71
|
+
"book.entries":"条目 · {n}",
|
|
72
|
+
"book.groups":"共 {n} 组",
|
|
73
|
+
"book.constant":"常驻",
|
|
74
|
+
"book.dupTitle":"成员卡之间的重复条目",
|
|
75
|
+
"book.dupNone":"成员卡之间没有内容重复的条目。",
|
|
76
|
+
"book.dupItem":"{n} 张卡都有 · 可省 {chars} 字符",
|
|
77
|
+
"book.dupMismatch":"名字/触发词不一致",
|
|
78
|
+
"book.promote":"提升到共享书",
|
|
79
|
+
"book.promoteHint":"把这 N 条搬进共享书,并从每张卡里删掉 —— 导出前先做这一步,卡里就不再有重复。",
|
|
80
|
+
"book.promotePreview":"将提升 {n} 条、从各卡共删 {m} 条重复",
|
|
81
|
+
"book.promoteDone":"已提升 {n} 条(删掉 {m} 条重复)",
|
|
82
|
+
"book.exportBook":"导出共享世界书 JSON",
|
|
83
|
+
"book.exported":"已导出 {name}",
|
|
84
|
+
"book.applyCard":"合并进当前卡",
|
|
85
|
+
"book.applyDone":"已往当前卡补了 {n} 条",
|
|
86
|
+
"book.applyNone":"当前卡已包含这本共享书的全部条目。",
|
|
87
|
+
"book.entryName":"条目名",
|
|
88
|
+
"book.entryKeys":"触发词(逗号分隔)",
|
|
89
|
+
"book.entryContent":"内容(可带 @@position / @@depth / @@activate 等装饰器)",
|
|
90
|
+
"book.refresh":"刷新",
|
|
91
|
+
"book.saved":"已保存(rev {rev})",
|
|
92
|
+
// —— 0.9.0 导出页的共享书块 ——
|
|
93
|
+
"export.book.title":"共享世界书",
|
|
94
|
+
"export.book.hint":"当前卡所属分组的共享设定可以单独导出(CCv3 lorebook_v3 格式):在客户端里把它绑成全局世界书,多张成员卡共用一本。",
|
|
95
|
+
"export.book.none":"当前卡不在任何分组里,或这个分组还没有共享世界书。",
|
|
96
|
+
"export.book.button":"⬇ 导出共享世界书",
|
|
97
|
+
"export.book.merge":"导出 JSON 时把共享条目并入这张卡(给不支持全局世界书的客户端)",
|
|
98
|
+
"export.book.merged":"(已并入 {n} 条共享条目)",
|
|
33
99
|
"action.expand":"✦ AI 扩写",
|
|
34
100
|
"action.validate":"校验",
|
|
35
101
|
"action.exportJson":"⬇ JSON",
|
|
@@ -137,6 +203,8 @@ window.__ModuleLoader__.load({
|
|
|
137
203
|
"workshop.group.none":"未分组",
|
|
138
204
|
"workshop.group.new":"+ 组",
|
|
139
205
|
"workshop.group.summary":"{n} 卡 / {m} 关系",
|
|
206
|
+
"workshop.group.newGroup":"+ 新建分组",
|
|
207
|
+
"workshop.group.empty":"(空)",
|
|
140
208
|
"workshop.group.needRestart":"宿主半还是旧代码(这些按钮要重启 dsh 才生效):请退出并重新打开桌面应用 / 重启 dsh web,然后刷新页面",
|
|
141
209
|
"workshop.group.prompt":"新分组名称(同一世界观的角色放一组)",
|
|
142
210
|
"workshop.group.assign":"组",
|
|
@@ -281,13 +349,63 @@ window.__ModuleLoader__.load({
|
|
|
281
349
|
"panel.title":"Character Card Studio",
|
|
282
350
|
"panel.subtitle":"CCv3 · Idea to Importable Card",
|
|
283
351
|
"panel.badge":"dsh-cc-studio · fusion",
|
|
284
|
-
"step.world":"World
|
|
352
|
+
"step.world":"World",
|
|
285
353
|
"step.char":"Character",
|
|
286
354
|
"step.lore":"Lorebook",
|
|
287
355
|
"step.export":"Validate & Export",
|
|
356
|
+
"step.overview":"Overview",
|
|
357
|
+
"step.greetings":"Openings & examples",
|
|
358
|
+
"step.rel":"Relations & groups",
|
|
359
|
+
"step.book":"Shared lorebook",
|
|
288
360
|
"step.world.desc":"Timeline/Factions/Geo/Power/Daily",
|
|
289
|
-
"step.char.desc":"
|
|
290
|
-
"step.lore.desc":"
|
|
361
|
+
"step.char.desc":"Core fields · tags · creator",
|
|
362
|
+
"step.lore.desc":"Entries · budget · keys",
|
|
363
|
+
"step.overview.desc":"Progress · stats · quick actions",
|
|
364
|
+
"step.greetings.desc":"First · alternates · group · examples",
|
|
365
|
+
"step.rel.desc":"Graph · members · render",
|
|
366
|
+
"step.book.desc":"One per group · dedupe",
|
|
367
|
+
"workshop.rel.edit":"Edit",
|
|
368
|
+
"workshop.rel.editTitle":"Edit relation",
|
|
369
|
+
"workshop.rel.addTitle":"Add relation",
|
|
370
|
+
"workshop.rel.typeLabel":"Type",
|
|
371
|
+
"workshop.rel.dirLabel":"Direction",
|
|
372
|
+
"workshop.rel.myCard":"current card",
|
|
373
|
+
"book.title":"Shared lorebook",
|
|
374
|
+
"book.hint":"One book per group: put the world setting (timeline / factions / geography / power / daily) here and keep only character-private entries in each member card. The export is CCv3's standalone lorebook_v3 JSON — bind it as a global lorebook in your client and every member card shares one copy instead of repeating it.",
|
|
375
|
+
"book.noGroup":"Shared lorebooks live on a group: create one under “Saved characters” on the right and put the member cards in it.",
|
|
376
|
+
"book.empty":"This group has no shared lorebook yet.",
|
|
377
|
+
"book.name":"Book name",
|
|
378
|
+
"book.desc":"Note",
|
|
379
|
+
"book.depth":"Scan depth",
|
|
380
|
+
"book.budget":"Token budget",
|
|
381
|
+
"book.tokens":"~{n} tokens",
|
|
382
|
+
"book.entries":"Entries · {n}",
|
|
383
|
+
"book.groups":"{n} groups",
|
|
384
|
+
"book.constant":"constant",
|
|
385
|
+
"book.dupTitle":"Entries duplicated across member cards",
|
|
386
|
+
"book.dupNone":"No entry is duplicated across member cards.",
|
|
387
|
+
"book.dupItem":"in {n} cards · saves {chars} chars",
|
|
388
|
+
"book.dupMismatch":"name/keys differ",
|
|
389
|
+
"book.promote":"Promote into the shared book",
|
|
390
|
+
"book.promoteHint":"Move these N entries into the shared book and delete them from every card — do this before exporting and the cards hold no duplicates.",
|
|
391
|
+
"book.promotePreview":"Will promote {n} and delete {m} duplicates across the cards",
|
|
392
|
+
"book.promoteDone":"Promoted {n} (removed {m} duplicates)",
|
|
393
|
+
"book.exportBook":"Export shared lorebook JSON",
|
|
394
|
+
"book.exported":"Exported {name}",
|
|
395
|
+
"book.applyCard":"Merge into the current card",
|
|
396
|
+
"book.applyDone":"Added {n} entries to the current card",
|
|
397
|
+
"book.applyNone":"The current card already contains every entry of this book.",
|
|
398
|
+
"book.entryName":"Entry name",
|
|
399
|
+
"book.entryKeys":"Keys (comma separated)",
|
|
400
|
+
"book.entryContent":"Content (may carry @@position / @@depth / @@activate decorators)",
|
|
401
|
+
"book.refresh":"Refresh",
|
|
402
|
+
"book.saved":"Saved (rev {rev})",
|
|
403
|
+
"export.book.title":"Shared lorebook",
|
|
404
|
+
"export.book.hint":"The shared setting of this card's group can be exported on its own (CCv3 lorebook_v3 format): bind it as a global lorebook in your client and every member card shares one copy.",
|
|
405
|
+
"export.book.none":"This card is in no group, or its group has no shared lorebook yet.",
|
|
406
|
+
"export.book.button":"⬇ Export shared lorebook",
|
|
407
|
+
"export.book.merge":"Merge the shared entries into this card when exporting JSON (for clients without a global lorebook)",
|
|
408
|
+
"export.book.merged":"({n} shared entries merged in)",
|
|
291
409
|
"action.expand":"AI Expand",
|
|
292
410
|
"action.validate":"Validate",
|
|
293
411
|
"action.exportJson":"⬇ JSON",
|
|
@@ -395,6 +513,8 @@ window.__ModuleLoader__.load({
|
|
|
395
513
|
"workshop.group.none":"Ungrouped",
|
|
396
514
|
"workshop.group.new":"+ Group",
|
|
397
515
|
"workshop.group.summary":"{n} cards / {m} relations",
|
|
516
|
+
"workshop.group.newGroup":"+ New group",
|
|
517
|
+
"workshop.group.empty":"(empty)",
|
|
398
518
|
"workshop.group.needRestart":"The host half is still running old code (these buttons need a dsh restart): quit and reopen the desktop app (or restart dsh web), then reload the page.",
|
|
399
519
|
"workshop.group.prompt":"New group name (characters of one world go together)",
|
|
400
520
|
"workshop.group.assign":"Group",
|
|
@@ -577,7 +697,7 @@ window.__ModuleLoader__.load({
|
|
|
577
697
|
var _saved = loadSettings();
|
|
578
698
|
// 兼容旧开关:poll=false 视为 0 秒
|
|
579
699
|
if(_saved.poll===false) _saved.pollInterval = 0;
|
|
580
|
-
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, currentLibraryId:null, editingWorld:null, editingWorldRevert:null, editingText:null, pollEnabled:_saved.poll, pollInterval:_saved.pollInterval, autoValidate:_saved.autoValidate, autoSaveOnImport:_saved.autoSaveOnImport };
|
|
700
|
+
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, 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 };
|
|
581
701
|
var listeners=new Set();
|
|
582
702
|
function emit(){ listeners.forEach(function(fn){ try{fn();}catch(e){console.error(e);} }); }
|
|
583
703
|
function getSnapshot(){ return state; }
|
|
@@ -776,10 +896,31 @@ window.__ModuleLoader__.load({
|
|
|
776
896
|
}catch(e){ set({lastMsg:String(e&&e.message||e)}); }
|
|
777
897
|
}
|
|
778
898
|
function importJSON(){ return importAny(); }
|
|
899
|
+
/** 条目内容比对(折叠空白后逐字)——导出时把共享条目并进卡用的,与两半同一套口径。 */
|
|
900
|
+
function sameEntryContent(a, b){
|
|
901
|
+
var norm=function(e){ return String((e&&e.content)!=null? e.content : "").replace(/\s+/g," ").trim(); };
|
|
902
|
+
var x=norm(a); return !!x && x===norm(b);
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* 导出 JSON。0.9.0:若在导出页勾了「并入共享条目」,则**只在导出的副本上**把该组的共享世界书
|
|
906
|
+
* 里卡里还没有的条目补进去(给不支持全局世界书的客户端兜底)——草稿本身一个字节都不动。
|
|
907
|
+
*/
|
|
779
908
|
function exportJSON(){
|
|
780
|
-
var
|
|
909
|
+
var out=state.draft, merged=0;
|
|
910
|
+
var book=state.book && state.book.book;
|
|
911
|
+
if(state.bookMerge && book && Array.isArray(book.data && book.data.entries) && book.data.entries.length){
|
|
912
|
+
var ents=(state.draft.data.character_book && state.draft.data.character_book.entries) || [];
|
|
913
|
+
var add=[];
|
|
914
|
+
book.data.entries.forEach(function(e){ if(!ents.some(function(x){ return sameEntryContent(x, e); })) add.push(e); });
|
|
915
|
+
if(add.length){
|
|
916
|
+
out=JSON.parse(JSON.stringify(state.draft));
|
|
917
|
+
out.data.character_book=Object.assign({}, out.data.character_book, { entries: ents.concat(add) });
|
|
918
|
+
merged=add.length;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
var blob=new Blob([JSON.stringify(out,null,2)],{type:"application/json"});
|
|
781
922
|
var a=document.createElement("a"); a.href=URL.createObjectURL(blob); a.download=(state.draft.data.name||"card")+".json"; a.click(); setTimeout(function(){ URL.revokeObjectURL(a.href); },800);
|
|
782
|
-
set({lastMsg:t("store.exportedJson")});
|
|
923
|
+
set({lastMsg: merged? t("store.exportedJson") + " " + t("export.book.merged",{n:String(merged)}) : t("store.exportedJson")});
|
|
783
924
|
}
|
|
784
925
|
function exportPNG(){
|
|
785
926
|
var sid=state.currentSessionId||null;
|
|
@@ -888,7 +1029,7 @@ window.__ModuleLoader__.load({
|
|
|
888
1029
|
var gid = String(id || "");
|
|
889
1030
|
set({currentGroupId: gid});
|
|
890
1031
|
// "" = 全部(不过滤)、"__none" = 只看未分组:这两个都不是真分组,没有图谱可载
|
|
891
|
-
if(gid && gid !== "__none") loadGroup(gid); else set({relations:null});
|
|
1032
|
+
if(gid && gid !== "__none"){ loadGroup(gid); loadBook(gid); } else { set({relations:null, book:null}); }
|
|
892
1033
|
}
|
|
893
1034
|
async function refreshGroups(){
|
|
894
1035
|
try{
|
|
@@ -901,6 +1042,13 @@ window.__ModuleLoader__.load({
|
|
|
901
1042
|
set({lastMsg: String((res && res.error && res.error.message) || "")});
|
|
902
1043
|
}catch(e){ set({lastMsg:String(e && e.message || e)}); }
|
|
903
1044
|
}
|
|
1045
|
+
/** 从某个分组面板直接进关系视图(折叠面板标题上的「关系」按钮用它)。 */
|
|
1046
|
+
function openRelationsFor(gid){
|
|
1047
|
+
var id = String(gid || "");
|
|
1048
|
+
if(!id || id === "__none"){ set({lastMsg: t("workshop.rel.pickGroup")}); return; }
|
|
1049
|
+
set({currentGroupId: id, cur: 4});
|
|
1050
|
+
loadGroup(id);
|
|
1051
|
+
}
|
|
904
1052
|
/** 0.8.1:从侧栏进关系视图。未选具体分组时**给一句提示**,而不是把按钮静默禁用(用户会以为坏了)。 */
|
|
905
1053
|
function openRelations(){
|
|
906
1054
|
var gid = String(state.currentGroupId || "");
|
|
@@ -919,6 +1067,78 @@ window.__ModuleLoader__.load({
|
|
|
919
1067
|
}catch(e){ set({lastMsg:String(e && e.message || e)}); }
|
|
920
1068
|
set({relBusy:null});
|
|
921
1069
|
}
|
|
1070
|
+
// ——— 0.9.0 组级共享世界书:一组一份,成员卡里只留私有条目 ———
|
|
1071
|
+
async function loadBook(groupId){
|
|
1072
|
+
var gid = String(groupId || state.currentGroupId || "");
|
|
1073
|
+
if(!gid){ set({book:null}); return; }
|
|
1074
|
+
set({bookBusy:"load"});
|
|
1075
|
+
try{
|
|
1076
|
+
var res = await rpc("cc_getBook", {groupId: gid});
|
|
1077
|
+
if(res && res.ok && res.value) set({book: res.value});
|
|
1078
|
+
else set({book:null, lastMsg:String((res && res.error && res.error.message) || "")});
|
|
1079
|
+
}catch(e){ set({lastMsg:String(e && e.message || e)}); }
|
|
1080
|
+
set({bookBusy:null});
|
|
1081
|
+
}
|
|
1082
|
+
async function patchBook(args){
|
|
1083
|
+
var gid = String(state.currentGroupId||"");
|
|
1084
|
+
if(!gid) return;
|
|
1085
|
+
set({bookBusy:"patch"});
|
|
1086
|
+
try{
|
|
1087
|
+
var res = await rpc("cc_patchBook", Object.assign({groupId: gid}, args||{}));
|
|
1088
|
+
if(res && res.ok && res.value){ await loadBook(gid); set({lastMsg:t("book.saved",{rev:String(res.value.rev)})}); }
|
|
1089
|
+
else set({lastMsg:String((res && res.error && res.error.message) || "")});
|
|
1090
|
+
}catch(e){ set({lastMsg:String(e && e.message || e)}); }
|
|
1091
|
+
set({bookBusy:null});
|
|
1092
|
+
}
|
|
1093
|
+
/** 提升去重:`apply` 为假时只扫描预览(宿主半默认也是 dryRun,两边都不写盘)。 */
|
|
1094
|
+
async function promoteBook(keys, apply){
|
|
1095
|
+
var gid = String(state.currentGroupId||"");
|
|
1096
|
+
if(!gid) return;
|
|
1097
|
+
set({bookBusy: apply? "promote" : "scan"});
|
|
1098
|
+
try{
|
|
1099
|
+
var res = await rpc("cc_promoteBook", {groupId: gid, apply: !!apply, keys: (keys && keys.length)? keys : undefined});
|
|
1100
|
+
if(res && res.ok && res.value){
|
|
1101
|
+
var v = res.value;
|
|
1102
|
+
set({lastMsg: v.dryRun
|
|
1103
|
+
? t("book.promotePreview",{n:String(v.wouldPromote||0), m:String(v.removedTotal||0)})
|
|
1104
|
+
: t("book.promoteDone",{n:String(v.promoted||0), m:String(v.removedTotal||0)})});
|
|
1105
|
+
await loadBook(gid);
|
|
1106
|
+
if(!v.dryRun){ await refreshLibrary(); await loadGroup(gid); }
|
|
1107
|
+
} else set({lastMsg:String((res && res.error && res.error.message) || "")});
|
|
1108
|
+
}catch(e){ set({lastMsg:String(e && e.message || e)}); }
|
|
1109
|
+
set({bookBusy:null});
|
|
1110
|
+
}
|
|
1111
|
+
async function applyBook(cardId){
|
|
1112
|
+
var gid = String(state.currentGroupId||"");
|
|
1113
|
+
if(!gid) return;
|
|
1114
|
+
set({bookBusy:"apply"});
|
|
1115
|
+
try{
|
|
1116
|
+
var res = await rpc("cc_applyBook", {groupId: gid, cardId: cardId||undefined});
|
|
1117
|
+
if(res && res.ok && res.value){
|
|
1118
|
+
var v = res.value;
|
|
1119
|
+
set({lastMsg: v.added ? t("book.applyDone",{n:String(v.added)}) : t("book.applyNone")});
|
|
1120
|
+
await refreshLibrary();
|
|
1121
|
+
} else set({lastMsg:String((res && res.error && res.error.message) || "")});
|
|
1122
|
+
}catch(e){ set({lastMsg:String(e && e.message || e)}); }
|
|
1123
|
+
set({bookBusy:null});
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* 导出共享世界书:文件本身就是 CCv3 官方的独立 lorebook 格式({spec:"lorebook_v3", data}),
|
|
1127
|
+
* 拷出去就能在聊天客户端里当全局/世界书绑 —— 绑定后所有成员卡共用这一本,卡里不必重复存。
|
|
1128
|
+
*/
|
|
1129
|
+
function exportBookFile(){
|
|
1130
|
+
var b = state.book && state.book.book;
|
|
1131
|
+
if(!b) return;
|
|
1132
|
+
try{
|
|
1133
|
+
var blob = new Blob([JSON.stringify({spec:"lorebook_v3", data:b.data}, null, 2)], {type:"application/json"});
|
|
1134
|
+
var a=document.createElement("a"); a.href=URL.createObjectURL(blob);
|
|
1135
|
+
a.download = String(b.data.name||"shared-lorebook").replace(/[\\/:*?"<>|]/g,"_") + ".lorebook.json";
|
|
1136
|
+
a.click();
|
|
1137
|
+
setTimeout(function(){ URL.revokeObjectURL(a.href); }, 800);
|
|
1138
|
+
set({lastMsg: t("book.exported",{name:String(b.data.name||"")})});
|
|
1139
|
+
}catch(e){ set({lastMsg:String(e && e.message || e)}); }
|
|
1140
|
+
}
|
|
1141
|
+
function setBookMerge(v){ set({bookMerge: !!v}); }
|
|
922
1142
|
async function createGroup(nameArg){
|
|
923
1143
|
// 0.8.1:不再依赖 window.prompt(内嵌 webview 会拦掉原生对话框 → 用户看到的是「点了没反应」)。
|
|
924
1144
|
// 名字由侧栏那个内联输入框传进来;没传就当取消。
|
|
@@ -1095,6 +1315,10 @@ window.__ModuleLoader__.load({
|
|
|
1095
1315
|
if(res && res.ok && res.value && res.value.draft){
|
|
1096
1316
|
setDraftFromHost(res.value.draft);
|
|
1097
1317
|
set({currentLibraryId: id});
|
|
1318
|
+
// 0.9.0:载入一张卡就**跟着切到它所属的分组**。实机反馈:右边载入了陆昭,中间「人物关系」
|
|
1319
|
+
// 却还停在江晚那个「测试」组,必须再点一次那个组的「关系」按钮才会切过去 —— 非常费解。
|
|
1320
|
+
// 关系视图与共享世界书都是**按组**看的,所以「换卡」必须带着组一起换(没有组就清空)。
|
|
1321
|
+
setCurrentGroupId((res.value.meta && res.value.meta.groupId) ? String(res.value.meta.groupId) : "");
|
|
1098
1322
|
set({lastMsg:t("store.loadedCard",{name:(res.value.meta.name||id), n:(res.value.draft.data.character_book.entries.length||0), id:id.slice(0,8)})});
|
|
1099
1323
|
} else set({lastMsg: res && res.error? String(res.error.message):t("store.errLoadFailed")});
|
|
1100
1324
|
}catch(e){ set({lastMsg: String(e && e.message || e)}); }
|
|
@@ -1137,7 +1361,7 @@ window.__ModuleLoader__.load({
|
|
|
1137
1361
|
// 0.4.1:这里原本还有一个 exportLibraryEntry —— 直接下载某张已存角色的 JSON。
|
|
1138
1362
|
// 三个导出入口归一之后,卡片上的「导出」改成切到导出页,导出动作只剩导出页那一套实现,
|
|
1139
1363
|
// 这条旁路就没有调用者了(留着它等于「同一次导出有两套行为」),故删除。
|
|
1140
|
-
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 };
|
|
1364
|
+
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, setBookMerge:setBookMerge };
|
|
1141
1365
|
}
|
|
1142
1366
|
|
|
1143
1367
|
var S={
|
|
@@ -1378,33 +1602,49 @@ window.__ModuleLoader__.load({
|
|
|
1378
1602
|
var jumpRef=React.useState(null); var jumpFrom=jumpRef[0], setJumpFrom=jumpRef[1];
|
|
1379
1603
|
// 0.8.0 关系视图的「添加关系」表单(局部状态,不进 store:它只是草稿输入)
|
|
1380
1604
|
var relFormRef=React.useState({from:"", to:"", type:"friend", strength:3, direction:"one-way", note:""}); var relForm=relFormRef[0], setRelForm=relFormRef[1];
|
|
1605
|
+
// 0.9.0:关系的「添加 / 编辑」改成二级弹窗(列表回到只读的「关系表」形态)
|
|
1606
|
+
var relEditRef=React.useState(null); var relEdit=relEditRef[0], setRelEdit=relEditRef[1];
|
|
1607
|
+
var relAddRef=React.useState(false); var relAddOpen=relAddRef[0], setRelAddOpen=relAddRef[1];
|
|
1381
1608
|
// 0.8.1:分组名 / NPC 名改用内联输入(不再用 window.prompt —— 内嵌 webview 会拦掉它)
|
|
1382
1609
|
var groupDraftRef=React.useState(null); var groupDraft=groupDraftRef[0], setGroupDraft=groupDraftRef[1];
|
|
1383
|
-
|
|
1610
|
+
var npcDraftRef=React.useState(""); var npcDraft=npcDraftRef[0], setNpcDraft=npcDraftRef[1];
|
|
1611
|
+
// 折叠面板:哪些分组被收起(键 = 分组 id,"" 表示未分组那一节)
|
|
1612
|
+
var collapsedRef=React.useState({}); var collapsedGroups=collapsedRef[0], setCollapsedGroups=collapsedRef[1];
|
|
1613
|
+
// 0.9.0 共享世界书的「+ 新条目」表单(局部状态,不进 store:只是草稿输入)
|
|
1614
|
+
var bookFormRef=React.useState({name:"", keys:"", content:""}); var bookForm=bookFormRef[0], setBookForm=bookFormRef[1];
|
|
1615
|
+
function toggleGroupCollapsed(gid){ var key=String(gid||""); var next=Object.assign({}, collapsedGroups); next[key]=!next[key]; setCollapsedGroups(next); }
|
|
1384
1616
|
React.useEffect(function(){ return store.subscribe(function(){ setS(store.getSnapshot()); }); }, [store]);
|
|
1385
1617
|
React.useEffect(function(){
|
|
1386
|
-
if(s.panelOpen){
|
|
1618
|
+
if(s.panelOpen){
|
|
1619
|
+
store.refreshLibrary(); store.refreshGroups();
|
|
1620
|
+
// 0.9.0:面板一开就把当前组的共享世界书拉回来(左侧徽章与共享书视图都靠它)
|
|
1621
|
+
if(String(s.currentGroupId||"") && s.currentGroupId !== "__none") store.loadBook();
|
|
1622
|
+
}
|
|
1387
1623
|
}, [s.panelOpen]);
|
|
1388
1624
|
if(!s.panelOpen) return null;
|
|
1389
|
-
//
|
|
1390
|
-
//
|
|
1391
|
-
var steps=[
|
|
1625
|
+
// 顶部胶囊(会话域那几个快捷入口):0.9.0 起跟左侧新结构走,取最常看的三处 ——
|
|
1626
|
+
// 总览(整卡状态)/ 角色 / 世界书。其余入口都在左侧导航里,胶囊不追求全。
|
|
1627
|
+
var steps=[{cur:5, key:"step.overview"}, {cur:1, key:"step.char"}, {cur:2, key:"step.lore"}];
|
|
1392
1628
|
// 步骤项的三个视觉状态严格互斥:选中(active) / 键盘焦点(focus-visible) / 悬停(hover)。
|
|
1393
1629
|
// 鼠标点击走 onMouseDown preventDefault,不把焦点抢到按钮上 —— 否则旧项的焦点环会留在原地,
|
|
1394
1630
|
// 与新的选中项同时发亮,看起来像「同时选中了两个」(这是本次修的问题)。
|
|
1395
|
-
function navItem(
|
|
1396
|
-
var active=s.cur===
|
|
1397
|
-
var hovered=hoverKey==="nav:"+
|
|
1398
|
-
var focused=focusKey==="nav:"+
|
|
1399
|
-
|
|
1631
|
+
function navItem(cur, title, desc, prog){
|
|
1632
|
+
var active=s.cur===cur;
|
|
1633
|
+
var hovered=hoverKey==="nav:"+cur;
|
|
1634
|
+
var focused=focusKey==="nav:"+cur;
|
|
1635
|
+
var dot = prog ? stateDot(prog.state) : null;
|
|
1636
|
+
return h("button",{key:"step:"+cur, type:"button", "aria-current": active? "step": undefined,
|
|
1400
1637
|
style: Object.assign({}, S.navBtn, hovered&&!active? S.navBtnHover:{}, focused? S.navBtnFocus:{}, active? S.navBtnActive:{}),
|
|
1401
1638
|
onMouseDown:function(e){ if(e && e.preventDefault) e.preventDefault(); },
|
|
1402
|
-
onMouseEnter:function(){ setHoverKey("nav:"+
|
|
1639
|
+
onMouseEnter:function(){ setHoverKey("nav:"+cur); },
|
|
1403
1640
|
onMouseLeave:function(){ setHoverKey(null); },
|
|
1404
|
-
onFocus:function(e){ setFocusKey(isFocusVisible(e)? "nav:"+
|
|
1641
|
+
onFocus:function(e){ setFocusKey(isFocusVisible(e)? "nav:"+cur : null); },
|
|
1405
1642
|
onBlur:function(){ setFocusKey(null); },
|
|
1406
|
-
onClick:function(){ store.setCur(
|
|
1407
|
-
}, title,
|
|
1643
|
+
onClick:function(){ store.setCur(cur); }
|
|
1644
|
+
}, title,
|
|
1645
|
+
dot? h("span",{style:{fontSize:11, color:dot.color, marginLeft:6}}, dot.text) : null,
|
|
1646
|
+
(prog && prog.note)? h("span",{style:{fontSize:10, color:"var(--dsw-alias-label-secondary)", marginLeft:6}}, prog.note) : null,
|
|
1647
|
+
h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", marginTop:2}}, desc));
|
|
1408
1648
|
}
|
|
1409
1649
|
// 工具按钮(⛶ / 删除…)鼠标按下就拦掉默认行为:别把焦点抢走,否则又留下一圈焦点描边
|
|
1410
1650
|
function stopFocusSteal(e){ if(e && e.preventDefault) e.preventDefault(); }
|
|
@@ -1486,6 +1726,31 @@ window.__ModuleLoader__.load({
|
|
|
1486
1726
|
})
|
|
1487
1727
|
);
|
|
1488
1728
|
}
|
|
1729
|
+
// 0.9.0 总览用的进度行:与工具半 workflow.state 同口径(三态),但纯客户端算 —— 省一次 RPC,
|
|
1730
|
+
// 也保证「左侧导航的徽章」和「总览页」永远一致(同一个函数算出来的)。
|
|
1731
|
+
function draftProgress(s2){
|
|
1732
|
+
var d = s2.draft.data;
|
|
1733
|
+
var entries = Array.isArray(d.character_book && d.character_book.entries) ? d.character_book.entries : [];
|
|
1734
|
+
var manual = entries.filter(function(e){ return !(e && (e.cc_auto || (e.extensions && e.extensions.cc_auto))); }).length;
|
|
1735
|
+
var filled = function(v){ return String(v==null? "": v).trim().length > 0; };
|
|
1736
|
+
var charN = [d.name, d.description, d.personality, d.scenario].filter(filled).length;
|
|
1737
|
+
var worldN = ["timeline","factions","geo","power","daily"].filter(function(k){ return filled(s2.world[k]); }).length;
|
|
1738
|
+
var alt = (d.alternate_greetings||[]).length, grp = (d.group_only_greetings||[]).length;
|
|
1739
|
+
var firstOk = filled(d.first_mes);
|
|
1740
|
+
var three = function(n, need){ return n>=need ? "ok" : (n>0 ? "partial" : "missing"); };
|
|
1741
|
+
return [
|
|
1742
|
+
{ cur:1, title:t("step.char"), state: three(charN,4), note: charN+"/4" },
|
|
1743
|
+
{ cur:0, title:t("step.world"), state: three(worldN,3), note: worldN+"/5" },
|
|
1744
|
+
{ cur:2, title:t("step.lore"), state: (entries.length>=5 && manual>=1) ? "ok" : (entries.length>0 ? "partial" : "missing"),
|
|
1745
|
+
note: t("common.entries",{n:String(entries.length)}) },
|
|
1746
|
+
{ cur:6, title:t("step.greetings"), state: (firstOk && alt>=2 && grp>=1) ? "ok" : ((firstOk||alt||grp) ? "partial" : "missing"),
|
|
1747
|
+
note: (firstOk?1:0)+"+"+alt+"+"+grp },
|
|
1748
|
+
];
|
|
1749
|
+
}
|
|
1750
|
+
function stateDot(state){
|
|
1751
|
+
return { color: state==="ok" ? "#a6e8c8" : (state==="partial" ? "#ffc46b" : "var(--dsw-alias-label-secondary)"),
|
|
1752
|
+
text: state==="ok" ? "✓" : (state==="partial" ? "◐" : "○") };
|
|
1753
|
+
}
|
|
1489
1754
|
var main=null;
|
|
1490
1755
|
if(s.cur===0){
|
|
1491
1756
|
// 五维世界观:默认直接读全文(无边框 textarea 自动撑高),⛶ 仍可放大框写长文
|
|
@@ -1536,7 +1801,135 @@ window.__ModuleLoader__.load({
|
|
|
1536
1801
|
field("system_prompt","system_prompt",{rowKey:"sys"}),
|
|
1537
1802
|
field("post_history_instructions","post_history_instructions",{rowKey:"phi"})
|
|
1538
1803
|
)
|
|
1539
|
-
)
|
|
1804
|
+
)
|
|
1805
|
+
);
|
|
1806
|
+
} else if(s.cur===5){
|
|
1807
|
+
// ── 总览(0.9.0 新增):一眼看完整张卡 + 快捷动作。写作已交给 LLM,工坊的入口应该是「状态」。
|
|
1808
|
+
var prog=draftProgress(s);
|
|
1809
|
+
main=h("div",null,
|
|
1810
|
+
h("div",{style:S.readCard},
|
|
1811
|
+
h("h3",{style:S.readTitle}, t("step.overview"),
|
|
1812
|
+
h("span",{style:{marginLeft:8, fontSize:12, fontWeight:400, color:"var(--dsw-alias-label-secondary)"}},
|
|
1813
|
+
"· "+(String(s.draft.data.name||"").trim() || t("workshop.library.unnamed")))),
|
|
1814
|
+
h("div",{style:{display:"flex", flexWrap:"wrap", gap:6, marginBottom:10}},
|
|
1815
|
+
(s.draft.data.tags||[]).map(function(tg){ return h("span",{key:String(tg), style:S.badge}, String(tg)); }),
|
|
1816
|
+
String(s.draft.data.creator||"").trim()? h("span",{style:S.badge}, "✎ "+String(s.draft.data.creator)) : null,
|
|
1817
|
+
(s.draftStatus && s.draftStatus.cardId)? h("span",{style:S.badge}, "ID:"+String(s.draftStatus.cardId).slice(0,8)) : null),
|
|
1818
|
+
prog.map(function(row){
|
|
1819
|
+
var dot=stateDot(row.state);
|
|
1820
|
+
return h("button",{key:"prog-"+row.cur, type:"button",
|
|
1821
|
+
style:{display:"flex", alignItems:"center", gap:10, width:"100%", textAlign:"left", background:"var(--dsw-alias-bg-base)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:10, padding:"8px 10px", marginBottom:6, cursor:"pointer", color:"var(--dsw-alias-label-primary)", font:"inherit"},
|
|
1822
|
+
onMouseDown:stopFocusSteal, onClick:function(){ store.setCur(row.cur); }},
|
|
1823
|
+
h("span",{style:{fontSize:14, width:16, textAlign:"center", color:dot.color}}, dot.text),
|
|
1824
|
+
h("span",{style:{flex:1, fontSize:13}}, row.title),
|
|
1825
|
+
h("span",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)"}}, row.note));
|
|
1826
|
+
}),
|
|
1827
|
+
h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", margin:"10px 0", lineHeight:1.7}}, t("step.overview.desc")),
|
|
1828
|
+
h("div",{style:{display:"flex", gap:8, flexWrap:"wrap"}},
|
|
1829
|
+
h("button",{type:"button", style:S.btn, onMouseDown:stopFocusSteal, onClick:function(){ store.validate(); }}, s.busy==="validate"? t("action.validating") : t("action.validate")),
|
|
1830
|
+
h("button",{type:"button", style:Object.assign({}, S.btn, S.btnGhost), onMouseDown:stopFocusSteal, onClick:function(){ store.setCur(3); }}, t("step.export")),
|
|
1831
|
+
h("button",{type:"button", style:Object.assign({}, S.btn, S.btnGhost), onMouseDown:stopFocusSteal, onClick:function(){ store.saveCurrentToLibrary(); }}, t("workshop.library.save"))),
|
|
1832
|
+
s.lastMsg? h("div",{style:{marginTop:10, fontSize:12, color:"var(--dsw-alias-label-secondary)", lineHeight:1.7}}, s.lastMsg):null)
|
|
1833
|
+
);
|
|
1834
|
+
} else if(s.cur===7){
|
|
1835
|
+
// ── 共享世界书(0.9.0):一组一份,成员卡里只留角色私有条目 ──
|
|
1836
|
+
var bk = s.book;
|
|
1837
|
+
var gidB = String(s.currentGroupId||"");
|
|
1838
|
+
var bookReady = !!(bk && bk.exists && bk.book);
|
|
1839
|
+
var dup = (bk && bk.scan && bk.scan.candidates) ? bk.scan.candidates : [];
|
|
1840
|
+
main=h("div",null,
|
|
1841
|
+
h("div",{style:S.readCard},
|
|
1842
|
+
h("h3",{style:S.readTitle}, t("book.title"),
|
|
1843
|
+
bookReady? h("span",{style:Object.assign({}, S.badge, {marginLeft:8})}, "rev "+bk.rev) : null,
|
|
1844
|
+
(bk && bk.group)? h("span",{style:{marginLeft:8, fontSize:12, fontWeight:400, color:"var(--dsw-alias-label-secondary)"}}, "· "+bk.group.name) : null),
|
|
1845
|
+
h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", marginBottom:10, lineHeight:1.7}}, t("book.hint")),
|
|
1846
|
+
!gidB
|
|
1847
|
+
? h("div",{style:{fontSize:12, color:"var(--dsw-alias-label-secondary)"}}, t("book.noGroup"))
|
|
1848
|
+
: h("div",null,
|
|
1849
|
+
// 书级参数:defaultValue + onBlur 提交 —— 不随每次击键发 RPC
|
|
1850
|
+
h("div",{style:{display:"grid", gridTemplateColumns:"1fr 1fr", gap:"0 18px"}},
|
|
1851
|
+
h("div",{style:S.fieldRow},
|
|
1852
|
+
h("div",{style:S.fieldLabel}, h("label",{style:S.fieldName}, t("book.name"))),
|
|
1853
|
+
h("input",{style:S.readInput, defaultValue: bookReady? bk.book.data.name : "", placeholder:t("workshop.field.empty"),
|
|
1854
|
+
onBlur:function(ev){ if(bookReady && ev.target.value !== bk.book.data.name) store.patchBook({name: ev.target.value}); }})),
|
|
1855
|
+
h("div",{style:S.fieldRow},
|
|
1856
|
+
h("div",{style:S.fieldLabel}, h("label",{style:S.fieldName}, t("book.budget"))),
|
|
1857
|
+
h("input",{style:S.readInput, defaultValue: bookReady? String(bk.book.data.token_budget) : "", placeholder:"2000",
|
|
1858
|
+
onBlur:function(ev){ var n=Number(ev.target.value); if(bookReady && n>0 && n!==bk.book.data.token_budget) store.patchBook({token_budget:n}); }})),
|
|
1859
|
+
h("div",{style:S.fieldRow},
|
|
1860
|
+
h("div",{style:S.fieldLabel}, h("label",{style:S.fieldName}, t("book.depth"))),
|
|
1861
|
+
h("input",{style:S.readInput, defaultValue: bookReady? String(bk.book.data.scan_depth) : "", placeholder:"4",
|
|
1862
|
+
onBlur:function(ev){ var n=Number(ev.target.value); if(bookReady && n>0 && n!==bk.book.data.scan_depth) store.patchBook({scan_depth:n}); }})),
|
|
1863
|
+
h("div",{style:S.fieldRow},
|
|
1864
|
+
h("div",{style:S.fieldLabel}, h("label",{style:S.fieldName}, t("book.desc"))),
|
|
1865
|
+
h("input",{style:S.readInput, defaultValue: bookReady? bk.book.data.description : "", placeholder:t("workshop.field.empty"),
|
|
1866
|
+
onBlur:function(ev){ if(bookReady && ev.target.value !== bk.book.data.description) store.patchBook({description: ev.target.value}); }}))),
|
|
1867
|
+
h("div",{style:{marginTop:12, marginBottom:6, fontSize:12, fontWeight:600}},
|
|
1868
|
+
t("book.entries",{n:String(bookReady? bk.book.data.entries.length : 0)}),
|
|
1869
|
+
bookReady? h("span",{style:Object.assign({}, S.badge, {marginLeft:8})}, t("book.tokens",{n:String(bk.tokens||0)})) : null),
|
|
1870
|
+
// ⚠️ 宿主端点给的是**原始文档**(book.data.entries)—— 预览与长度在客户端算、删除按 index 定位。
|
|
1871
|
+
// 别再读成顶层字段:关系视图就是这么踩过一次(数据在 group 里、界面却显示 0 条)。
|
|
1872
|
+
bookReady? h("div",null, bk.book.data.entries.map(function(e, ei){
|
|
1873
|
+
var txt = String(e.content||"");
|
|
1874
|
+
return h("div",{key:ei, style:{border:"1px solid var(--dsw-alias-border-l1)", borderRadius:10, padding:"8px 10px", marginBottom:8, background:"var(--dsw-alias-bg-base)"}},
|
|
1875
|
+
h("div",{style:{display:"flex", alignItems:"center", gap:6, flexWrap:"wrap"}},
|
|
1876
|
+
h("span",{style:{fontWeight:600, fontSize:12}}, e.name||"—"),
|
|
1877
|
+
h("span",{style:Object.assign({}, S.badge, {fontSize:10})}, e.constant? t("book.constant") : ((e.keys||[]).join(", ")||"—")),
|
|
1878
|
+
h("span",{style:{fontSize:10, color:"var(--dsw-alias-label-secondary)"}}, String(txt.length)+" chars"),
|
|
1879
|
+
h("button",{type:"button", style:Object.assign({}, S.btnGhost, {marginLeft:"auto", padding:"2px 8px", fontSize:11, border:"1px solid #3a2a2a", color:"#e8a6a6", background:"transparent"}), onMouseDown:stopFocusSteal,
|
|
1880
|
+
onClick:function(){ store.patchBook({ops:[{op:"removeEntry", index:ei}]}); }}, t("common.delete"))),
|
|
1881
|
+
h("div",{style:{fontSize:12, lineHeight:1.7, marginTop:4, whiteSpace:"pre-wrap", wordBreak:"break-word", color:"var(--dsw-alias-label-secondary)"}}, txt.slice(0,80).replace(/\s+/g," ") + (txt.length>80? " …":"")));
|
|
1882
|
+
})) : null,
|
|
1883
|
+
// 新条目:内联表单(不用 window.prompt —— 内嵌 webview 会拦掉原生对话框,表现为「点了没反应」)
|
|
1884
|
+
h("div",{style:{marginTop:6, paddingTop:10, borderTop:"1px dashed var(--dsw-alias-border-l1)"}},
|
|
1885
|
+
h("div",{style:{display:"flex", gap:6, flexWrap:"wrap", alignItems:"center"}},
|
|
1886
|
+
h("input",{placeholder:t("book.entryName"), value:bookForm.name, style:{width:120, background:"var(--dsw-alias-bg-base)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:8, color:"var(--dsw-alias-label-primary)", padding:"4px 8px", fontSize:12}, onChange:function(ev){ setBookForm(Object.assign({}, bookForm, {name: ev.target.value})); }}),
|
|
1887
|
+
h("input",{placeholder:t("book.entryKeys"), value:bookForm.keys, style:{width:180, background:"var(--dsw-alias-bg-base)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:8, color:"var(--dsw-alias-label-primary)", padding:"4px 8px", fontSize:12}, onChange:function(ev){ setBookForm(Object.assign({}, bookForm, {keys: ev.target.value})); }}),
|
|
1888
|
+
h("button",{type:"button", style:Object.assign({}, S.btn, {padding:"4px 12px", fontSize:12}), onMouseDown:stopFocusSteal,
|
|
1889
|
+
disabled: s.bookBusy==="patch",
|
|
1890
|
+
onClick:function(){
|
|
1891
|
+
var content=String(bookForm.content||"").trim();
|
|
1892
|
+
if(!content) return;
|
|
1893
|
+
store.patchBook({ops:[{op:"addEntry", entry:{ name:String(bookForm.name||"").trim(),
|
|
1894
|
+
keys:String(bookForm.keys||"").split(",").map(function(x){ return x.trim(); }).filter(Boolean), content: content }}]});
|
|
1895
|
+
setBookForm({name:"", keys:"", content:""});
|
|
1896
|
+
}}, s.bookBusy==="patch"? "…" : t("action.addEntry"))),
|
|
1897
|
+
h("textarea",{placeholder:t("book.entryContent"), value:bookForm.content, style:Object.assign({}, S.textarea, {minHeight:70, marginTop:6}),
|
|
1898
|
+
onChange:function(ev){ setBookForm(Object.assign({}, bookForm, {content: ev.target.value})); }})),
|
|
1899
|
+
// 成员卡之间的重复条目 → 一键提升(去重的正解)
|
|
1900
|
+
h("div",{style:{marginTop:14, paddingTop:10, borderTop:"1px dashed var(--dsw-alias-border-l1)"}},
|
|
1901
|
+
h("div",{style:{fontSize:12, fontWeight:600, marginBottom:4}}, t("book.dupTitle")),
|
|
1902
|
+
dup.length===0
|
|
1903
|
+
? h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)"}}, t("book.dupNone"))
|
|
1904
|
+
: h("div",null,
|
|
1905
|
+
h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", marginBottom:6, lineHeight:1.7}}, t("book.promoteHint")),
|
|
1906
|
+
dup.map(function(c, i){
|
|
1907
|
+
return h("div",{key:c.signature||i, style:{border:"1px solid var(--dsw-alias-border-l1)", borderRadius:10, padding:"8px 10px", marginBottom:6, background:"var(--dsw-alias-bg-base)"}},
|
|
1908
|
+
h("div",{style:{display:"flex", alignItems:"center", gap:6, flexWrap:"wrap"}},
|
|
1909
|
+
h("span",{style:{fontWeight:600, fontSize:12}}, c.name||"—"),
|
|
1910
|
+
h("span",{style:Object.assign({}, S.badge, {fontSize:10})}, t("book.dupItem",{n:String(c.cards), chars:String(c.savedChars||0)})),
|
|
1911
|
+
(c.nameMismatch||c.keysMismatch)? h("span",{style:Object.assign({}, S.badge, {fontSize:10, color:"#ffc46b", borderColor:"#ffc46b"})}, t("book.dupMismatch")) : null),
|
|
1912
|
+
h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", marginTop:4, whiteSpace:"pre-wrap", wordBreak:"break-word"}}, c.preview));
|
|
1913
|
+
}),
|
|
1914
|
+
h("div",{style:{display:"flex", gap:8, marginTop:8, flexWrap:"wrap"}},
|
|
1915
|
+
h("button",{type:"button", style:Object.assign({}, S.btn, {padding:"4px 12px", fontSize:12}), onMouseDown:stopFocusSteal,
|
|
1916
|
+
disabled: !!(s.bookBusy==="scan"||s.bookBusy==="promote"),
|
|
1917
|
+
onClick:function(){ store.promoteBook(null, true); }}, (s.bookBusy==="promote")? "…" : t("book.promote")))))),
|
|
1918
|
+
s.lastMsg? h("div",{style:{marginTop:10, fontSize:12, color:"var(--dsw-alias-label-secondary)", lineHeight:1.7}}, s.lastMsg):null),
|
|
1919
|
+
(gidB && bookReady) ? h("div",{style:S.readCard},
|
|
1920
|
+
h("h3",{style:S.readTitle}, t("export.book.title")),
|
|
1921
|
+
h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", marginBottom:10, lineHeight:1.7}}, t("export.book.hint")),
|
|
1922
|
+
h("div",{style:{display:"flex", gap:8, flexWrap:"wrap"}},
|
|
1923
|
+
h("button",{type:"button", style:S.btn, onMouseDown:stopFocusSteal, onClick:function(){ store.exportBookFile(); }}, t("export.book.button")),
|
|
1924
|
+
h("button",{type:"button", style:Object.assign({}, S.btn, S.btnGhost), onMouseDown:stopFocusSteal, disabled: s.bookBusy==="apply", onClick:function(){ store.applyBook(); }}, s.bookBusy==="apply"? "…" : t("book.applyCard")),
|
|
1925
|
+
h("button",{type:"button", style:Object.assign({}, S.btn, S.btnGhost), onMouseDown:stopFocusSteal, onClick:function(){ store.loadBook(); }}, t("book.refresh"))),
|
|
1926
|
+
s.lastMsg? h("div",{style:{marginTop:10, fontSize:12, color:"var(--dsw-alias-label-secondary)"}}, s.lastMsg):null) : null
|
|
1927
|
+
);
|
|
1928
|
+
} else if(s.cur===6){
|
|
1929
|
+
// 0.9.0:开场与示例从「角色」页拆出来 —— 左侧导航改按**卡片结构**排(角色 / 世界观 / 世界书 /
|
|
1930
|
+
// 开场与示例 / 关系与分组 / 共享世界书),不再按「写作第几步」排:写作已交给 LLM,
|
|
1931
|
+
// 工坊现在的职责是「看 + 微调 + 导出」,按结构分页比按步骤分页好找。
|
|
1932
|
+
main=h("div",null,
|
|
1540
1933
|
h("div",{style:S.readCard},
|
|
1541
1934
|
h("h3",{style:S.readTitle},t("workshop.char.openingTitle")),
|
|
1542
1935
|
field("first_mes","first_mes",{rowKey:"first"}),
|
|
@@ -1593,6 +1986,30 @@ window.__ModuleLoader__.load({
|
|
|
1593
1986
|
h("button",{style: Object.assign({}, S.btn, S.btnGhost), onClick:function(){ store.validate(); }}, s.busy==="validate"? t("action.validating"): t("action.validate")),
|
|
1594
1987
|
h("span",{style:{color:"var(--dsw-alias-label-secondary)", fontSize:11, alignSelf:"center"}}, t("workshop.export.hint2"))
|
|
1595
1988
|
),
|
|
1989
|
+
// 0.9.0:组级共享世界书 —— 导出独立文件(零重复的做法),或按需把共享条目并进这张卡
|
|
1990
|
+
(function(){
|
|
1991
|
+
var bk=s.book, gid=String(s.currentGroupId||"");
|
|
1992
|
+
var ready=!!(bk && bk.exists && bk.book);
|
|
1993
|
+
if(!gid || !ready){
|
|
1994
|
+
return h("div",{style:{fontSize:11, margin:"-4px 0 10px", lineHeight:1.7, color:"var(--dsw-alias-label-secondary)"}},
|
|
1995
|
+
t("export.book.title") + " · " + t("export.book.none"));
|
|
1996
|
+
}
|
|
1997
|
+
return h("div",{style:{border:"1px solid var(--dsw-alias-border-l1)", borderRadius:10, padding:"10px 12px", margin:"-4px 0 12px", background:"var(--dsw-alias-bg-base)"}},
|
|
1998
|
+
h("div",{style:{display:"flex", alignItems:"center", gap:8, flexWrap:"wrap"}},
|
|
1999
|
+
h("strong",{style:{fontSize:12}}, t("export.book.title")),
|
|
2000
|
+
// ⚠️ 端点给的是**原始文档**(book.data.*)。这里曾经写成 `bk.book.entries` —— 于是
|
|
2001
|
+
// 任何「当前组有共享书」的卡一进导出页就抛 TypeError,整个工坊闪退、连 dsh 一起卡死
|
|
2002
|
+
// (0.8.2 实机事故;同一形状错误在关系视图与导航徽章上各犯过一次,别再读顶层)。
|
|
2003
|
+
h("span",{style:S.badge}, bk.book.data.name||"—"),
|
|
2004
|
+
h("span",{style:S.badge}, t("book.entries",{n:String(bk.book.data.entries.length)})),
|
|
2005
|
+
h("span",{style:S.badge}, t("book.tokens",{n:String(bk.tokens||0)}))),
|
|
2006
|
+
h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", margin:"6px 0 8px", lineHeight:1.7}}, t("export.book.hint")),
|
|
2007
|
+
h("div",{style:{display:"flex", gap:8, flexWrap:"wrap", alignItems:"center"}},
|
|
2008
|
+
h("button",{type:"button", style:S.btn, onMouseDown:stopFocusSteal, onClick:function(){ store.exportBookFile(); }}, t("export.book.button")),
|
|
2009
|
+
h("label",{style:{display:"flex", alignItems:"center", gap:6, fontSize:11, color:"var(--dsw-alias-label-secondary)", cursor:"pointer"}},
|
|
2010
|
+
h("input",{type:"checkbox", checked:!!s.bookMerge, onChange:function(ev){ store.setBookMerge(ev.target.checked); }}),
|
|
2011
|
+
t("export.book.merge"))));
|
|
2012
|
+
})(),
|
|
1596
2013
|
// 导出页只认「当前工坊里的卡」:右侧卡片上的「导出」也是切到这一页,所以这里把口径写清楚
|
|
1597
2014
|
// 从卡片跳进来、且那张卡不是工坊里这张时,把口径说透(导出的是谁、要导它该怎么做);
|
|
1598
2015
|
// 其余情况只留常态说明。
|
|
@@ -1612,26 +2029,81 @@ window.__ModuleLoader__.load({
|
|
|
1612
2029
|
// ── 关系视图(s.cur === 4)──
|
|
1613
2030
|
// 与导出页同属「不在左侧导航里的视图」:入口是右侧分组选择器旁的「关系」按钮。
|
|
1614
2031
|
// 这里编辑的是**插件侧图谱**(<DSH_HOME>/cc-groups/);只有「渲染到当前卡」才会把关系写进 description。
|
|
1615
|
-
|
|
1616
|
-
|
|
2032
|
+
// ⚠️ 节点与边在 `relations.group` **里面** —— 宿主半 cc_getGroup 的返回是 { group, members, rev }。
|
|
2033
|
+
// 曾经按工具半 cc_get_relations 的形状读顶层的 s.relations.nodes / .edges,于是**永远读到空数组**:
|
|
2034
|
+
// 界面显示「关系 · 0」、NPC 名字区空白,而 rev 与成员数正常(一个在 group 里、一个在顶层)——
|
|
2035
|
+
// 看起来像「卡里的关系没读到」,实际是客户端读错了返回形状。
|
|
2036
|
+
var relGroup = (s.relations && s.relations.group) || null;
|
|
2037
|
+
var relNodes = (relGroup && relGroup.nodes) || [];
|
|
2038
|
+
var relEdges = (relGroup && relGroup.edges) || [];
|
|
1617
2039
|
var relMembers = (s.relations && s.relations.members) || [];
|
|
1618
|
-
var relGroup = s.relations && s.relations.group;
|
|
1619
2040
|
var gid = String(s.currentGroupId || "");
|
|
1620
2041
|
var canEdit = !!gid && gid !== "__none";
|
|
1621
2042
|
var relTypes = ["family","romance","mentor","friend","colleague","rival","enemy","debt","superior","other"];
|
|
1622
2043
|
var nodeName = function(id){ var hit = relNodes.filter(function(x){ return x.id === id; })[0]; return hit ? hit.name : id; };
|
|
1623
2044
|
var setF = function(patch){ setRelForm(Object.assign({}, relForm, patch)); };
|
|
1624
2045
|
var smallSel = {fontSize:11, background:"transparent", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:6, color:"var(--dsw-alias-label-primary)", padding:"1px 4px"};
|
|
2046
|
+
// 「当前卡」在组里的节点(用来在成员区标出「我」、并给「添加关系」挑默认的 from)
|
|
2047
|
+
var myNode = relNodes.filter(function(n){ return n.kind === "card" && n.cardId === String(s.currentLibraryId||""); })[0];
|
|
2048
|
+
var myNodeId = myNode ? myNode.id : "";
|
|
2049
|
+
var relLabelOf = function(e){ return (e && e.label && String(e.label).trim()) ? String(e.label).trim() : t("rel.type."+((e && e.type) || "other")); };
|
|
2050
|
+
/**
|
|
2051
|
+
* 关系编辑的二级弹窗(添加 / 修改共用)。
|
|
2052
|
+
* 为什么挪出来:原来每行关系都摆着「类型 / 强度 / 方向」三个下拉 + ✕,12 条关系就把整页占满,
|
|
2053
|
+
* 读起来也不像「关系表」。现在列表是一行只读文本 + 一枚「编辑」,改哪条点哪条。
|
|
2054
|
+
* 弹窗用 position:fixed 明确覆盖全屏(不依赖 S.overlay 的定位方式,因为它可能挂在主区里)。
|
|
2055
|
+
*/
|
|
2056
|
+
function relDialog(titleKey, val, setVal, onSave, onDelete, withNodes){
|
|
2057
|
+
var setV = function(patch){ setVal(Object.assign({}, val, patch)); };
|
|
2058
|
+
var selStyle = {fontSize:12, background:"var(--dsw-alias-bg-base)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:8, color:"var(--dsw-alias-label-primary)", padding:"4px 8px"};
|
|
2059
|
+
var labStyle = {fontSize:11, color:"var(--dsw-alias-label-secondary)"};
|
|
2060
|
+
var close = function(){ setVal(null); };
|
|
2061
|
+
return h("div",{style:{position:"fixed", inset:0, background:"rgba(0,0,0,.55)", zIndex:45, display:"flex", alignItems:"center", justifyContent:"center"},
|
|
2062
|
+
onClick:function(e){ if(e.target===e.currentTarget) close(); }},
|
|
2063
|
+
h("div",{style:{width:"min(470px, 92vw)", background:"var(--dsw-alias-bg-layer-1)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:16, padding:"16px", boxShadow:"var(--dsw-shadow-lv3)"}},
|
|
2064
|
+
h("div",{style:{display:"flex", justifyContent:"space-between", alignItems:"center", marginBottom:12}},
|
|
2065
|
+
h("strong",{style:{fontSize:14, color:"var(--dsw-alias-label-primary)"}}, t(titleKey)),
|
|
2066
|
+
h("button",{type:"button", style:Object.assign({}, S.btnGhost, {padding:"4px 10px"}), onMouseDown:stopFocusSteal, onClick:close}, t("action.closeX"))),
|
|
2067
|
+
withNodes ? h("div",{style:{display:"grid", gridTemplateColumns:"1fr 1fr", gap:10, marginBottom:10}},
|
|
2068
|
+
h("label",{style:labStyle}, t("workshop.rel.from"),
|
|
2069
|
+
h("select",{style:Object.assign({}, selStyle, {width:"100%", marginTop:4}), value:val.from, onChange:function(ev){ setV({from:ev.target.value}); }},
|
|
2070
|
+
relNodes.map(function(n){ return h("option",{key:n.id, value:n.id}, n.name); }))),
|
|
2071
|
+
h("label",{style:labStyle}, t("workshop.rel.to"),
|
|
2072
|
+
h("select",{style:Object.assign({}, selStyle, {width:"100%", marginTop:4}), value:val.to, onChange:function(ev){ setV({to:ev.target.value}); }},
|
|
2073
|
+
relNodes.map(function(n){ return h("option",{key:n.id, value:n.id}, n.name); })))) : null,
|
|
2074
|
+
h("div",{style:{display:"grid", gridTemplateColumns:"1fr 1fr 1fr", gap:10}},
|
|
2075
|
+
h("label",{style:labStyle}, t("workshop.rel.typeLabel"),
|
|
2076
|
+
h("select",{style:Object.assign({}, selStyle, {width:"100%", marginTop:4}), value:val.type, onChange:function(ev){ setV({type:ev.target.value}); }},
|
|
2077
|
+
relTypes.map(function(ty){ return h("option",{key:ty, value:ty}, t("rel.type."+ty)); }))),
|
|
2078
|
+
h("label",{style:labStyle}, t("workshop.rel.strength"),
|
|
2079
|
+
h("select",{style:Object.assign({}, selStyle, {width:"100%", marginTop:4}), value:String(val.strength), onChange:function(ev){ setV({strength:Number(ev.target.value)}); }},
|
|
2080
|
+
[1,2,3,4,5].map(function(n){ return h("option",{key:n, value:String(n)}, n+"/5"); }))),
|
|
2081
|
+
h("label",{style:labStyle}, t("workshop.rel.dirLabel"),
|
|
2082
|
+
h("select",{style:Object.assign({}, selStyle, {width:"100%", marginTop:4}), value:val.direction, onChange:function(ev){ setV({direction:ev.target.value}); }},
|
|
2083
|
+
h("option",{value:"one-way"}, t("workshop.rel.dirOneWay")),
|
|
2084
|
+
h("option",{value:"mutual"}, t("workshop.rel.dirMutual"))))),
|
|
2085
|
+
h("input",{placeholder:t("workshop.rel.note"), value:val.note||"", style:Object.assign({}, selStyle, {width:"100%", boxSizing:"border-box", marginTop:10}), onChange:function(ev){ setV({note:ev.target.value}); }}),
|
|
2086
|
+
h("div",{style:{display:"flex", gap:8, marginTop:14, justifyContent:"space-between"}},
|
|
2087
|
+
onDelete ? h("button",{type:"button", style:Object.assign({}, S.btnGhost, {border:"1px solid rgba(224,85,85,.55)", color:"#e05555", background:"transparent", padding:"4px 12px"}), onMouseDown:stopFocusSteal, onClick:onDelete}, t("common.delete")) : h("span",null),
|
|
2088
|
+
h("div",{style:{display:"flex", gap:8}},
|
|
2089
|
+
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)", padding:"4px 12px"}), onMouseDown:stopFocusSteal, onClick:close}, t("action.cancel")),
|
|
2090
|
+
h("button",{type:"button", style:Object.assign({}, S.btn, {padding:"4px 14px"}), onMouseDown:stopFocusSteal, onClick:function(){ onSave(val); }}, t("action.done"))))));
|
|
2091
|
+
}
|
|
1625
2092
|
main = h("div",null,
|
|
1626
2093
|
h("div",{style:S.readCard},
|
|
1627
2094
|
h("h3",{style:S.readTitle}, t("workshop.rel.title"),
|
|
1628
2095
|
h("span",{style:{marginLeft:8, fontSize:12, fontWeight:400, color:"var(--dsw-alias-label-secondary)"}}, "· " + ((relGroup && relGroup.name) || t("workshop.rel.noGroup"))),
|
|
1629
|
-
relGroup ? h("span",{style:Object.assign({}, S.badge, {marginLeft:8})}, "rev " + relGroup.rev) : null
|
|
2096
|
+
relGroup ? h("span",{style:Object.assign({}, S.badge, {marginLeft:8})}, "rev " + relGroup.rev) : null,
|
|
2097
|
+
// 0.9.0:把「这是哪张卡的视角」写在标题上 —— 关系是按组看的,很容易看错对象
|
|
2098
|
+
myNode ? h("span",{style:{marginLeft:8, fontSize:12, fontWeight:400, color:"#7c5cff"}}, t("workshop.rel.myCard") + ":" + nodeName(myNodeId)) : null),
|
|
1630
2099
|
h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", marginBottom:10, lineHeight:1.7}}, t("workshop.rel.hint")),
|
|
1631
2100
|
!canEdit ? h("div",{style:{fontSize:12, color:"var(--dsw-alias-label-secondary)"}}, t("workshop.rel.pickGroup")) : h("div",null,
|
|
1632
2101
|
h("div",{style:{fontSize:12, fontWeight:600, marginBottom:4}}, t("workshop.rel.members") + " · " + relMembers.length),
|
|
1633
2102
|
h("div",{style:{display:"flex", flexWrap:"wrap", gap:6, marginBottom:14, alignItems:"center"}},
|
|
1634
|
-
relMembers.map(function(m){
|
|
2103
|
+
relMembers.map(function(m){
|
|
2104
|
+
var isMe = String(m.id) === String(s.currentLibraryId||"");
|
|
2105
|
+
return h("span",{key:m.id, style:Object.assign({}, S.badge, isMe? {borderColor:"#7c5cff", color:"#7c5cff"}:{})}, m.name + (isMe? " · " + t("workshop.rel.myCard") : ""));
|
|
2106
|
+
}),
|
|
1635
2107
|
relMembers.length===0 ? h("span",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)"}}, t("workshop.rel.noMember")) : null),
|
|
1636
2108
|
h("div",{style:{display:"flex", flexWrap:"wrap", gap:6, marginBottom:14, alignItems:"center"}},
|
|
1637
2109
|
relNodes.filter(function(n){ return n.kind === "npc"; }).map(function(n){ return h("span",{key:n.id, style:S.badge}, n.name); }),
|
|
@@ -1642,66 +2114,54 @@ window.__ModuleLoader__.load({
|
|
|
1642
2114
|
var name = String(npcDraft||"").trim();
|
|
1643
2115
|
if(name){ store.patchRelations([{op:"addNode", kind:"npc", name:name}]); setNpcDraft(""); }
|
|
1644
2116
|
}}, t("workshop.rel.addNode"))),
|
|
1645
|
-
h("div",{style:{
|
|
2117
|
+
h("div",{style:{display:"flex", alignItems:"center", gap:8, marginBottom:6}},
|
|
2118
|
+
h("div",{style:{fontSize:12, fontWeight:600, flex:1}}, t("workshop.rel.edges") + " · " + relEdges.length),
|
|
2119
|
+
// 「添加关系」挪进二级弹窗:原来那张内联表单要占两行,把列表挤得很长
|
|
2120
|
+
h("button",{type:"button", style:Object.assign({}, S.btn, {padding:"3px 10px", fontSize:11}), onMouseDown:stopFocusSteal,
|
|
2121
|
+
onClick:function(){
|
|
2122
|
+
var fromId = myNodeId || ((relNodes[0] && relNodes[0].id) || "");
|
|
2123
|
+
var other = relNodes.filter(function(n){ return n.id !== fromId; })[0];
|
|
2124
|
+
setRelForm({ from: fromId, to: other ? other.id : "", type: "friend", strength: 3, direction: "one-way", note: "" });
|
|
2125
|
+
setRelAddOpen(true);
|
|
2126
|
+
}}, t("workshop.rel.add"))),
|
|
1646
2127
|
relEdges.length===0 ? h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", marginBottom:10}}, t("workshop.rel.noEdge")) :
|
|
2128
|
+
// 一行只读 + 一枚「编辑」:类型/强度/方向/备注都在弹窗里改,列表因此回到「关系表」的样子
|
|
1647
2129
|
h("div",null, relEdges.map(function(e){
|
|
1648
|
-
return h("div",{key:e.id, style:{borderBottom:"1px solid var(--dsw-alias-border-l1)"
|
|
1649
|
-
h("
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
h("select",{style:smallSel, value:e.direction, onChange:function(ev){ store.patchRelations([{op:"updateEdge", edgeId:e.id, direction:ev.target.value}]); }},
|
|
1658
|
-
h("option",{value:"one-way"}, t("workshop.rel.dirOneWay")),
|
|
1659
|
-
h("option",{value:"mutual"}, t("workshop.rel.dirMutual"))),
|
|
1660
|
-
h("button",{type:"button", style:Object.assign({}, S.btnGhost, {border:"1px solid #3a2a2a", background:"transparent", color:"#e8a6a6", borderRadius:6, padding:"1px 6px", fontSize:11}), onClick:function(){ store.patchRelations([{op:"removeEdge", edgeId:e.id}]); }}, "✕")),
|
|
1661
|
-
e.note ? h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", marginTop:2}}, e.note) : null);
|
|
2130
|
+
return h("div",{key:e.id, style:{display:"flex", alignItems:"center", gap:8, padding:"5px 0", borderBottom:"1px solid var(--dsw-alias-border-l1)"}},
|
|
2131
|
+
h("span",{style:{fontSize:12, flexShrink:0}}, nodeName(e.from)),
|
|
2132
|
+
h("span",{style:{color:"#7c5cff", flexShrink:0}}, e.direction === "mutual" ? "⇄" : "→"),
|
|
2133
|
+
h("span",{style:{fontSize:12, flexShrink:0}}, nodeName(e.to)),
|
|
2134
|
+
h("span",{style:Object.assign({}, S.badge, {fontSize:10, flexShrink:0})}, relLabelOf(e)),
|
|
2135
|
+
h("span",{style:Object.assign({}, S.badge, {fontSize:10, flexShrink:0})}, e.strength+"/5"),
|
|
2136
|
+
h("span",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", flex:1, overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap"}}, e.note||""),
|
|
2137
|
+
h("button",{type:"button", style:Object.assign({}, S.btnGhost, {flexShrink:0, padding:"2px 8px", fontSize:11, border:"1px solid var(--dsw-alias-border-l1)", color:"var(--dsw-alias-label-primary)", background:"transparent"}), onMouseDown:stopFocusSteal,
|
|
2138
|
+
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")));
|
|
1662
2139
|
})),
|
|
1663
|
-
h("div",{style:{marginTop:14, paddingTop:10, borderTop:"1px dashed var(--dsw-alias-border-l1)"}},
|
|
1664
|
-
h("div",{style:{fontSize:12, fontWeight:600, marginBottom:6}}, t("workshop.rel.add")),
|
|
1665
|
-
h("div",{style:{display:"flex", gap:6, flexWrap:"wrap", alignItems:"center"}},
|
|
1666
|
-
h("span",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)"}}, t("workshop.rel.from")),
|
|
1667
|
-
h("select",{style:Object.assign({}, smallSel, {background:"var(--dsw-alias-bg-base)", maxWidth:130}), value:relForm.from, onChange:function(ev){ setF({from:ev.target.value}); }},
|
|
1668
|
-
h("option",{value:""}, "—"),
|
|
1669
|
-
relNodes.map(function(n){ return h("option",{key:n.id, value:n.id}, n.name); })),
|
|
1670
|
-
h("span",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)"}}, t("workshop.rel.to")),
|
|
1671
|
-
h("select",{style:Object.assign({}, smallSel, {background:"var(--dsw-alias-bg-base)", maxWidth:130}), value:relForm.to, onChange:function(ev){ setF({to:ev.target.value}); }},
|
|
1672
|
-
h("option",{value:""}, "—"),
|
|
1673
|
-
relNodes.map(function(n){ return h("option",{key:n.id, value:n.id}, n.name); })),
|
|
1674
|
-
h("select",{style:Object.assign({}, smallSel, {background:"var(--dsw-alias-bg-base)"}), value:relForm.type, onChange:function(ev){ setF({type:ev.target.value}); }},
|
|
1675
|
-
relTypes.map(function(ty){ return h("option",{key:ty, value:ty}, t("rel.type."+ty)); })),
|
|
1676
|
-
h("select",{style:Object.assign({}, smallSel, {background:"var(--dsw-alias-bg-base)"}), value:String(relForm.strength), onChange:function(ev){ setF({strength:Number(ev.target.value)}); }},
|
|
1677
|
-
[1,2,3,4,5].map(function(n){ return h("option",{key:n, value:String(n)}, t("workshop.rel.strength") + " " + n); })),
|
|
1678
|
-
h("select",{style:Object.assign({}, smallSel, {background:"var(--dsw-alias-bg-base)"}), value:relForm.direction, onChange:function(ev){ setF({direction:ev.target.value}); }},
|
|
1679
|
-
h("option",{value:"one-way"}, t("workshop.rel.dirOneWay")),
|
|
1680
|
-
h("option",{value:"mutual"}, t("workshop.rel.dirMutual"))),
|
|
1681
|
-
h("button",{type:"button", style:Object.assign({}, S.btn, {padding:"3px 10px", fontSize:11}), onClick:function(){ store.patchRelations([{op:"addEdge", from:relForm.from, to:relForm.to, type:relForm.type, strength:relForm.strength, direction:relForm.direction, note:relForm.note}]); }}, t("workshop.rel.add"))),
|
|
1682
|
-
h("input",{placeholder:t("workshop.rel.note"), style:{width:"100%", boxSizing:"border-box", marginTop:6, background:"var(--dsw-alias-bg-base)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:8, color:"var(--dsw-alias-label-primary)", padding:"4px 8px", fontSize:12}, value:relForm.note, onChange:function(ev){ setF({note:ev.target.value}); }})),
|
|
1683
2140
|
h("div",{style:{display:"flex", gap:8, marginTop:14, flexWrap:"wrap"}},
|
|
1684
2141
|
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")),
|
|
1685
2142
|
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")),
|
|
1686
2143
|
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")),
|
|
1687
2144
|
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")))),
|
|
1688
|
-
s.lastMsg ? h("div",{style:{marginTop:10, fontSize:12, color:"var(--dsw-alias-label-secondary)", lineHeight:1.7}}, s.lastMsg) : null
|
|
2145
|
+
s.lastMsg ? h("div",{style:{marginTop:10, fontSize:12, color:"var(--dsw-alias-label-secondary)", lineHeight:1.7}}, s.lastMsg) : null,
|
|
2146
|
+
// —— 二级弹窗:改一条关系 / 加一条关系(列表只读,动作都在这里)——
|
|
2147
|
+
relEdit ? relDialog("workshop.rel.editTitle", relEdit, setRelEdit,
|
|
2148
|
+
function(v){ store.patchRelations([{op:"updateEdge", edgeId:v.edgeId, type:v.type, strength:v.strength, direction:v.direction, note:v.note}]); setRelEdit(null); },
|
|
2149
|
+
function(){ store.patchRelations([{op:"removeEdge", edgeId:relEdit.edgeId}]); setRelEdit(null); }, false) : null,
|
|
2150
|
+
relAddOpen ? relDialog("workshop.rel.addTitle", relForm,
|
|
2151
|
+
function(v){ if(v===null){ setRelAddOpen(false); return; } setRelForm(v); },
|
|
2152
|
+
function(v){ if(!v.from || !v.to || v.from===v.to) return; store.patchRelations([{op:"addEdge", from:v.from, to:v.to, type:v.type, strength:v.strength, direction:v.direction, note:v.note}]); setRelAddOpen(false); }, null, true) : null)
|
|
1689
2153
|
);
|
|
1690
2154
|
}
|
|
1691
|
-
var stepsEls=steps.map(function(
|
|
1692
|
-
var active=s.cur===
|
|
2155
|
+
var stepsEls=steps.map(function(it, i){
|
|
2156
|
+
var active=s.cur===it.cur;
|
|
1693
2157
|
var hovered=hoverKey==="step:"+i;
|
|
1694
2158
|
return h("span",{key:i, style: Object.assign({}, S.step, hovered&&!active? S.stepHover:{}, active? S.stepActive:{}),
|
|
1695
2159
|
onMouseEnter:function(){ setHoverKey("step:"+i); },
|
|
1696
2160
|
onMouseLeave:function(){ setHoverKey(null); },
|
|
1697
|
-
onClick:function(){ store.setCur(
|
|
2161
|
+
onClick:function(){ store.setCur(it.cur); }}, (i+1)+". "+t(it.key));
|
|
1698
2162
|
});
|
|
1699
2163
|
var filteredLibrary = (function(){
|
|
1700
|
-
var gid = String(s.currentGroupId||"");
|
|
1701
2164
|
var base = s.library;
|
|
1702
|
-
// 0.8.0:分组筛选("" = 全部;"__none" = 只看未分组)
|
|
1703
|
-
if(gid === "__none") base = base.filter(function(e){ return !e.groupId; });
|
|
1704
|
-
else if(gid) base = base.filter(function(e){ return String(e.groupId||"")===gid; });
|
|
1705
2165
|
var q = String(s.libraryQuery||"").trim().toLowerCase();
|
|
1706
2166
|
if(!q) return base;
|
|
1707
2167
|
return base.filter(function(e){
|
|
@@ -1712,7 +2172,83 @@ window.__ModuleLoader__.load({
|
|
|
1712
2172
|
});
|
|
1713
2173
|
})();
|
|
1714
2174
|
var bodyStyle = Object.assign({}, S.body, {gridTemplateColumns: s.libraryCollapsed ? "200px 1fr 40px" : "200px 1fr 268px"});
|
|
1715
|
-
|
|
2175
|
+
// 单张已存卡的渲染(抽成函数:分组折叠面板与搜索结果共用)
|
|
2176
|
+
function renderCardEntry(entry){
|
|
2177
|
+
var isBusy = s.libraryBusy && String(s.libraryBusy).indexOf(entry.id)>=0;
|
|
2178
|
+
var isCurrent = s.currentLibraryId===entry.id;
|
|
2179
|
+
// 列表展示前 4 个标签,「还有几个没显示」用 meta.tagsTotal 算(meta.tags 本身截断到 6 个 —— P1-11)
|
|
2180
|
+
var tagTotal = (typeof entry.tagsTotal==="number") ? entry.tagsTotal : (entry.tags||[]).length;
|
|
2181
|
+
var tagShown = Math.min((entry.tags||[]).length, 4);
|
|
2182
|
+
var itemStyle = isCurrent ? Object.assign({}, S.libraryItem, {borderColor:"#7c5cff", background:"color-mix(in srgb, #7c5cff 8%, var(--dsw-alias-bg-layer-2))"}) : S.libraryItem;
|
|
2183
|
+
return h("div",{key:entry.id, style:itemStyle},
|
|
2184
|
+
h("div",{style:{display:"flex", alignItems:"center", justifyContent:"space-between", gap:6}},
|
|
2185
|
+
h("div",{style:{fontWeight:600, fontSize:12, overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap", flex:1}}, entry.name),
|
|
2186
|
+
h("span",{style:Object.assign({}, S.badge, {flexShrink:0})}, t("common.loreCount").replace("{n}", String(entry.loreCount)))
|
|
2187
|
+
),
|
|
2188
|
+
h("div",{style:{fontSize:10, color:"var(--dsw-alias-label-secondary)", marginTop:2, fontFamily:"ui-monospace,Menlo,monospace"}}, "ID:"+entry.id.slice(0,8)),
|
|
2189
|
+
h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", marginTop:2, whiteSpace:"nowrap", overflow:"hidden", textOverflow:"ellipsis"}}, entry.descriptionSnippet || (entry.tags||[]).join(" · ") || "—"),
|
|
2190
|
+
h("div",{style:{display:"flex", flexWrap:"wrap", gap:4, marginTop:6}},
|
|
2191
|
+
(entry.tags||[]).slice(0,4).map(function(t2){ return h("span",{key:t2, style:Object.assign({}, S.badge, {fontSize:10, padding:"1px 5px"})}, t2); }),
|
|
2192
|
+
tagTotal > tagShown ? h("span",{style:{fontSize:10, color:"var(--dsw-alias-label-secondary)"}}, "+"+(tagTotal-tagShown)) : null
|
|
2193
|
+
),
|
|
2194
|
+
h("div",{style:{fontSize:10, color:"var(--dsw-alias-label-secondary)", marginTop:4}}, new Date(entry.updatedAt*1000).toLocaleString()),
|
|
2195
|
+
h("div",{style:{display:"flex", gap:6, marginTop:8}},
|
|
2196
|
+
h("button",{style:Object.assign({}, S.btn, {padding:"4px 8px", fontSize:11, flex:1}), onClick:function(){ store.loadFromLibrary(entry.id); }, disabled: !!isBusy}, isBusy?"…":t("workshop.library.load")),
|
|
2197
|
+
// 「导出」保留原位、只切到导出页(0.4.1 归一):导出页导出的始终是当前工坊里的卡
|
|
2198
|
+
h("button",{style:Object.assign({}, S.btnGhost, {border:"1px solid var(--dsw-alias-border-l1)", background:"transparent", color:(jumpFrom && jumpFrom.id===entry.id)? "#a6e8c8" : "var(--dsw-alias-label-secondary)", borderRadius:8, padding:"4px 8px", fontSize:11}), title:t("workshop.library.exportTip"), onClick:function(){ jumpToExport(entry); }}, (jumpFrom && jumpFrom.id===entry.id)? t("workshop.library.exportJumped"): t("workshop.library.export")),
|
|
2199
|
+
h("button",{style:Object.assign({}, S.btnGhost, {border:"1px solid #3a2a2a", background:"transparent", color:"#e8a6a6", borderRadius:8, padding:"4px 8px", fontSize:11}), onClick:function(){ store.deleteFromLibrary(entry.id); }, disabled: !!isBusy}, t("common.delete")),
|
|
2200
|
+
h("select",{style:{background:"transparent", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:8, color:(entry.groupId?"#7c5cff":"var(--dsw-alias-label-secondary)"), fontSize:11, padding:"2px 4px", maxWidth:92, flexShrink:0}, value:String(entry.groupId||""), disabled: !!isBusy, title:t("workshop.group.assign"), onChange:function(ev){ store.assignGroup(entry.id, ev.target.value||null); }},
|
|
2201
|
+
h("option",{value:""}, t("workshop.group.none")),
|
|
2202
|
+
(s.groups||[]).map(function(g){ return h("option",{key:g.id, value:g.id}, g.name); }))
|
|
2203
|
+
)
|
|
2204
|
+
);
|
|
2205
|
+
}
|
|
2206
|
+
/**
|
|
2207
|
+
* 已存角色列表(折叠面板版):**一个分组一节**,节标题就是分组名、点一下折叠/展开;
|
|
2208
|
+
* 没归组的卡拉进「未分组」那一节(永远显示,即使为空)。搜索时平铺全部命中项 —— 那时折叠只会碍事。
|
|
2209
|
+
*/
|
|
2210
|
+
function renderGroupedList(){
|
|
2211
|
+
var q = String(s.libraryQuery||"").trim().toLowerCase();
|
|
2212
|
+
var all = s.library || [];
|
|
2213
|
+
if(q){
|
|
2214
|
+
var hits = all.filter(function(e){
|
|
2215
|
+
var nameMatch = String(e.name||"").toLowerCase().indexOf(q)>=0;
|
|
2216
|
+
var tagMatch = Array.isArray(e.tags) && e.tags.some(function(x){ return String(x).toLowerCase().indexOf(q)>=0; });
|
|
2217
|
+
var loreMatch = String(e.loreCount||"").indexOf(q)>=0;
|
|
2218
|
+
return nameMatch || tagMatch || loreMatch;
|
|
2219
|
+
});
|
|
2220
|
+
if(!hits.length) return h("div",{style:{fontSize:12, color:"var(--dsw-alias-label-secondary)", textAlign:"center", padding:"24px 8px"}}, t("workshop.library.noMatch"));
|
|
2221
|
+
return h("div",{style:{display:"flex", flexDirection:"column", gap:6}}, hits.map(renderCardEntry));
|
|
2222
|
+
}
|
|
2223
|
+
if(!all.length) return h("div",{style:{fontSize:12, color:"var(--dsw-alias-label-secondary)", textAlign:"center", padding:"24px 8px"}}, t("workshop.library.empty"));
|
|
2224
|
+
var byGroup = {};
|
|
2225
|
+
all.forEach(function(e){ var g = String(e.groupId||""); if(!byGroup[g]) byGroup[g] = []; byGroup[g].push(e); });
|
|
2226
|
+
var sections = (s.groups||[]).map(function(g){ return { id:g.id, name:g.name, items: byGroup[g.id]||[], hasBook: !!g.hasBook, bookEntries: g.bookEntries||0 }; });
|
|
2227
|
+
sections.push({ id:"", name: t("workshop.group.none"), items: byGroup[""]||[], hasBook:false, bookEntries:0 });
|
|
2228
|
+
var out = sections.map(function(sec){
|
|
2229
|
+
var collapsed = !!collapsedGroups[sec.id];
|
|
2230
|
+
return h("div",{key:"sec:"+sec.id, style:{marginBottom:6}},
|
|
2231
|
+
h("div",{style:{display:"flex", alignItems:"center", gap:6, padding:"5px 6px", cursor:"pointer", borderRadius:8, background:"var(--dsw-alias-bg-layer-1)", border:"1px solid var(--dsw-alias-border-l1)"}, onClick:function(){ toggleGroupCollapsed(sec.id); }},
|
|
2232
|
+
h("span",{style:{fontSize:10, color:"var(--dsw-alias-label-secondary)", width:10, flexShrink:0}}, collapsed ? "▸" : "▾"),
|
|
2233
|
+
h("span",{style:{fontSize:12, fontWeight:600, flex:1, overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap"}}, sec.name),
|
|
2234
|
+
h("span",{style:{fontSize:10, color:"var(--dsw-alias-label-secondary)", flexShrink:0}}, String(sec.items.length)),
|
|
2235
|
+
// 0.9.0:这组有共享世界书时挂一枚入口(点它切到共享书视图并选中这个组)
|
|
2236
|
+
(sec.id && sec.hasBook) ? h("button",{type:"button", style:Object.assign({}, S.btnGhost, {border:"1px solid #7c5cff", background:"transparent", color:"#7c5cff", borderRadius:6, padding:"1px 6px", fontSize:10, flexShrink:0}), title:t("step.book"), onClick:function(ev){ ev.stopPropagation(); store.setCurrentGroupId(sec.id); store.setCur(7); }}, "📖 "+String(sec.bookEntries||0)) : null,
|
|
2237
|
+
sec.id ? h("button",{type:"button", style:Object.assign({}, S.btnGhost, {border:"1px solid #7c5cff", background:"transparent", color:"#7c5cff", borderRadius:6, padding:"1px 6px", fontSize:10, flexShrink:0}), onClick:function(ev){ ev.stopPropagation(); store.openRelationsFor(sec.id); }}, t("workshop.rel.open")) : null),
|
|
2238
|
+
collapsed ? null : h("div",{style:{paddingLeft:2, display:"flex", flexDirection:"column", gap:6, marginTop:6}},
|
|
2239
|
+
sec.items.length ? sec.items.map(renderCardEntry)
|
|
2240
|
+
: h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", padding:"2px 8px"}}, t("workshop.group.empty")))
|
|
2241
|
+
);
|
|
2242
|
+
});
|
|
2243
|
+
out.push(h("div",{key:"new-group", style:{marginTop:8}},
|
|
2244
|
+
groupDraft === null
|
|
2245
|
+
? h("button",{type:"button", style:Object.assign({}, S.btnGhost, {width:"100%", border:"1px dashed var(--dsw-alias-border-l1)", background:"transparent", color:"var(--dsw-alias-label-secondary)", borderRadius:8, padding:"6px 8px", fontSize:11}), onClick:function(){ setGroupDraft(""); }}, t("workshop.group.newGroup"))
|
|
2246
|
+
: h("input",{autoFocus:true, value:groupDraft, placeholder:t("workshop.group.prompt"), style:{width:"100%", boxSizing:"border-box", background:"var(--dsw-alias-bg-base)", border:"1px solid #7c5cff", borderRadius:8, color:"var(--dsw-alias-label-primary)", padding:"6px 8px", fontSize:11},
|
|
2247
|
+
onChange:function(ev){ setGroupDraft(ev.target.value); },
|
|
2248
|
+
onKeyDown:function(ev){ if(ev.key==="Enter"){ store.createGroup(ev.target.value); setGroupDraft(null); } if(ev.key==="Escape"){ setGroupDraft(null); } },
|
|
2249
|
+
onBlur:function(ev){ var v=String(ev.target.value||"").trim(); if(v) store.createGroup(v); setGroupDraft(null); }})));
|
|
2250
|
+
return h("div",null, out);
|
|
2251
|
+
} function renderLibrary(){
|
|
1716
2252
|
if(s.libraryCollapsed){
|
|
1717
2253
|
return h("div",{style:S.libraryCollapsedBar},
|
|
1718
2254
|
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")}, "◀"),
|
|
@@ -1750,58 +2286,10 @@ window.__ModuleLoader__.load({
|
|
|
1750
2286
|
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"))
|
|
1751
2287
|
),
|
|
1752
2288
|
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); }}),
|
|
1753
|
-
|
|
1754
|
-
h("div",{style:{display:"flex", gap:6, marginTop:6}},
|
|
1755
|
-
h("select",{style:{flex:1, minWidth:0, background:"var(--dsw-alias-bg-base)", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:8, color:"var(--dsw-alias-label-primary)", padding:"4px 6px", fontSize:12}, value:s.currentGroupId||"", onChange:function(e){ store.setCurrentGroupId(e.target.value); }},
|
|
1756
|
-
h("option",{value:""}, t("workshop.group.all")),
|
|
1757
|
-
h("option",{value:"__none"}, t("workshop.group.none")),
|
|
1758
|
-
(s.groups||[]).map(function(g){ return h("option",{key:g.id, value:g.id}, g.name + " · " + t("workshop.group.summary",{n:String(g.memberCount), m:String(g.edgeCount)})); })),
|
|
1759
|
-
(groupDraft === null
|
|
1760
|
-
? 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 8px", fontSize:11, flexShrink:0}), onClick:function(){ setGroupDraft(""); }}, t("workshop.group.new"))
|
|
1761
|
-
: h("input",{autoFocus:true, value:groupDraft, placeholder:t("workshop.group.prompt"), style:{width:104, flexShrink:0, background:"var(--dsw-alias-bg-base)", border:"1px solid #7c5cff", borderRadius:8, color:"var(--dsw-alias-label-primary)", padding:"4px 6px", fontSize:11},
|
|
1762
|
-
onChange:function(ev){ setGroupDraft(ev.target.value); },
|
|
1763
|
-
onKeyDown:function(ev){ if(ev.key==="Enter"){ store.createGroup(ev.target.value); setGroupDraft(null); } if(ev.key==="Escape"){ setGroupDraft(null); } },
|
|
1764
|
-
onBlur:function(ev){ var v=String(ev.target.value||"").trim(); if(v) store.createGroup(v); setGroupDraft(null); }})),
|
|
1765
|
-
h("button",{type:"button", style:Object.assign({}, S.btnGhost, {border:"1px solid #7c5cff", background:"transparent", color:"#7c5cff", borderRadius:8, padding:"4px 8px", fontSize:11, flexShrink:0}), onClick:function(){ store.openRelations(); }}, t("workshop.rel.open"))),
|
|
2289
|
+
|
|
1766
2290
|
s.lastMsg ? h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", whiteSpace:"nowrap", overflow:"hidden", textOverflow:"ellipsis"}}, s.lastMsg) : null
|
|
1767
2291
|
),
|
|
1768
|
-
h("div",{style:S.libraryList},
|
|
1769
|
-
filteredLibrary.length===0 ? h("div",{style:{fontSize:12, color:"var(--dsw-alias-label-secondary)", textAlign:"center", padding:"24px 8px"}}, s.library.length===0 ? t("workshop.library.empty") : t("workshop.library.noMatch")) :
|
|
1770
|
-
filteredLibrary.map(function(entry){
|
|
1771
|
-
var isBusy = s.libraryBusy && String(s.libraryBusy).indexOf(entry.id)>=0;
|
|
1772
|
-
var isCurrent = s.currentLibraryId===entry.id;
|
|
1773
|
-
// 列表展示前 4 个标签,但「还有几个没显示」必须用 meta.tagsTotal 算:meta.tags 本身截断到 6 个,
|
|
1774
|
-
// 拿它的长度当总数会偏小(卡里 7 个、列表只显示 6 个,用户会以为标签没存上 —— LLM 侧回测报的 P1-11)
|
|
1775
|
-
var tagTotal = (typeof entry.tagsTotal==="number") ? entry.tagsTotal : (entry.tags||[]).length;
|
|
1776
|
-
var tagShown = Math.min((entry.tags||[]).length, 4);
|
|
1777
|
-
var itemStyle = isCurrent ? Object.assign({}, S.libraryItem, {borderColor:"#7c5cff", background:"color-mix(in srgb, #7c5cff 8%, var(--dsw-alias-bg-layer-2))"}) : S.libraryItem;
|
|
1778
|
-
return h("div",{key:entry.id, style:itemStyle},
|
|
1779
|
-
h("div",{style:{display:"flex", alignItems:"center", justifyContent:"space-between", gap:6}},
|
|
1780
|
-
h("div",{style:{fontWeight:600, fontSize:12, overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap", flex:1}}, entry.name),
|
|
1781
|
-
h("span",{style:Object.assign({}, S.badge, {flexShrink:0})}, t("common.loreCount").replace("{n}", String(entry.loreCount)))
|
|
1782
|
-
),
|
|
1783
|
-
h("div",{style:{fontSize:10, color:"var(--dsw-alias-label-secondary)", marginTop:2, fontFamily:"ui-monospace,Menlo,monospace"}}, "ID:"+entry.id.slice(0,8)),
|
|
1784
|
-
h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", marginTop:2, whiteSpace:"nowrap", overflow:"hidden", textOverflow:"ellipsis"}}, entry.descriptionSnippet || entry.tags.join(" · ") || "—"),
|
|
1785
|
-
h("div",{style:{display:"flex", flexWrap:"wrap", gap:4, marginTop:6}},
|
|
1786
|
-
(entry.tags||[]).slice(0,4).map(function(t){ return h("span",{key:t, style:Object.assign({}, S.badge, {fontSize:10, padding:"1px 5px"})}, t); }),
|
|
1787
|
-
tagTotal > tagShown ? h("span",{style:{fontSize:10, color:"var(--dsw-alias-label-secondary)"}}, "+"+(tagTotal-tagShown)) : null
|
|
1788
|
-
),
|
|
1789
|
-
h("div",{style:{fontSize:10, color:"var(--dsw-alias-label-secondary)", marginTop:4}}, new Date(entry.updatedAt*1000).toLocaleString()),
|
|
1790
|
-
h("div",{style:{display:"flex", gap:6, marginTop:8}},
|
|
1791
|
-
h("button",{style:Object.assign({}, S.btn, {padding:"4px 8px", fontSize:11, flex:1}), onClick:function(){ store.loadFromLibrary(entry.id); }, disabled: !!isBusy}, isBusy?"…":t("workshop.library.load")),
|
|
1792
|
-
// 「导出」保留在原位,但改成切到导出页(0.4.1 归一):导出页导出的始终是当前工坊里的卡,
|
|
1793
|
-
// 想导这张已存角色先点「载入」—— 免得这里悄悄换掉用户正在编辑的草稿。
|
|
1794
|
-
// 点下去按钮自己先变(「已切到 ✓」),不然切页发生在中栏、用户视线却在右侧,会以为没反应。
|
|
1795
|
-
h("button",{style:Object.assign({}, S.btnGhost, {border:"1px solid var(--dsw-alias-border-l1)", background:"transparent", color:(jumpFrom && jumpFrom.id===entry.id)? "#a6e8c8" : "var(--dsw-alias-label-secondary)", borderRadius:8, padding:"4px 8px", fontSize:11}), title:t("workshop.library.exportTip"), onClick:function(){ jumpToExport(entry); }}, (jumpFrom && jumpFrom.id===entry.id)? t("workshop.library.exportJumped"): t("workshop.library.export")),
|
|
1796
|
-
h("button",{style:Object.assign({}, S.btnGhost, {border:"1px solid #3a2a2a", background:"transparent", color:"#e8a6a6", borderRadius:8, padding:"4px 8px", fontSize:11}), onClick:function(){ store.deleteFromLibrary(entry.id); }, disabled: !!isBusy}, t("common.delete")),
|
|
1797
|
-
// 0.8.0:直接把卡归到某个分组(空 = 未分组)。分组是关系图谱的载体。
|
|
1798
|
-
h("select",{style:{background:"transparent", border:"1px solid var(--dsw-alias-border-l1)", borderRadius:8, color:(entry.groupId?"#7c5cff":"var(--dsw-alias-label-secondary)"), fontSize:11, padding:"2px 4px", maxWidth:92, flexShrink:0}, value:String(entry.groupId||""), disabled: !!isBusy, title:t("workshop.group.assign"), onChange:function(ev){ store.assignGroup(entry.id, ev.target.value||null); }},
|
|
1799
|
-
h("option",{value:""}, t("workshop.group.none")),
|
|
1800
|
-
(s.groups||[]).map(function(g){ return h("option",{key:g.id, value:g.id}, g.name); }))
|
|
1801
|
-
)
|
|
1802
|
-
);
|
|
1803
|
-
})
|
|
1804
|
-
)
|
|
2292
|
+
h("div",{style:S.libraryList}, renderGroupedList())
|
|
1805
2293
|
);
|
|
1806
2294
|
}
|
|
1807
2295
|
return h("div",{style:S.overlay,
|
|
@@ -1863,9 +2351,33 @@ window.__ModuleLoader__.load({
|
|
|
1863
2351
|
})(),
|
|
1864
2352
|
h("div",{style:bodyStyle},
|
|
1865
2353
|
h("div",{style:S.nav},
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
2354
|
+
// 0.9.0:左侧从「写作三步」改成**卡片结构 + 进度徽章**。写作早交给 LLM 了,
|
|
2355
|
+
// 工坊现在的职责是「看 + 微调 + 导出」,所以按卡片组成分页,每项右侧挂三态进度点与计数。
|
|
2356
|
+
(function(){
|
|
2357
|
+
var prog = draftProgress(s);
|
|
2358
|
+
var byCur = {};
|
|
2359
|
+
prog.forEach(function(p){ byCur[p.cur] = p; });
|
|
2360
|
+
var curGid = String(s.currentGroupId||"");
|
|
2361
|
+
var curGroup = null;
|
|
2362
|
+
for(var i=0;i<s.groups.length;i++){ if(String(s.groups[i].id)===curGid) curGroup = s.groups[i]; }
|
|
2363
|
+
var relProg = (s.relations && s.relations.group)
|
|
2364
|
+
// ⚠️ 边在 group **里面**(宿主 cc_getGroup 的形状是 {group,members,rev})——
|
|
2365
|
+
// 这里曾经读顶层 s.relations.edges,于是左侧永远显示「0 条」(视图里却有 12 条)。
|
|
2366
|
+
? { state: (((s.relations.group.edges||[]).length)? "ok":"partial"), note: t("common.entries",{n:String((s.relations.group.edges||[]).length)}) }
|
|
2367
|
+
: { state: "missing", note: s.groups.length? t("book.groups",{n:String(s.groups.length)}) : "—" };
|
|
2368
|
+
var bookProg = (curGroup && curGroup.hasBook)
|
|
2369
|
+
? { state: (curGroup.bookEntries? "ok":"partial"), note: t("common.entries",{n:String(curGroup.bookEntries||0)}) }
|
|
2370
|
+
: { state: "missing", note: "—" };
|
|
2371
|
+
return [
|
|
2372
|
+
navItem(5, t("step.overview"), t("step.overview.desc"), null),
|
|
2373
|
+
navItem(1, t("step.char"), t("step.char.desc"), byCur[1]),
|
|
2374
|
+
navItem(0, t("step.world"), t("step.world.desc"), byCur[0]),
|
|
2375
|
+
navItem(2, t("step.lore"), t("step.lore.desc"), byCur[2]),
|
|
2376
|
+
navItem(6, t("step.greetings"), t("step.greetings.desc"), byCur[6]),
|
|
2377
|
+
navItem(4, t("step.rel"), t("step.rel.desc"), relProg),
|
|
2378
|
+
navItem(7, t("step.book"), t("step.book.desc"), bookProg)
|
|
2379
|
+
];
|
|
2380
|
+
})(),
|
|
1869
2381
|
h("div",{style:{marginTop:"auto", paddingTop:12, borderTop:"1px solid var(--dsw-alias-border-l1)", display:"flex", flexDirection:"column", gap:8}},
|
|
1870
2382
|
// 「校验导出」入口:这一步原本是导航里的第 4 项,与这枚按钮重复,0.4.1 合并成一枚。
|
|
1871
2383
|
// 点它切到导出页(校验按钮在那一页里)。
|