@xia-sc/dsh-cc-studio 0.7.1 → 0.8.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 +13 -5
- package/lib/agent.js +508 -4
- package/lib/client.js +305 -6
- package/lib/index.js +519 -4
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -132,6 +132,50 @@ window.__ModuleLoader__.load({
|
|
|
132
132
|
"workshop.library.save":"★ 保存当前",
|
|
133
133
|
"workshop.library.saveAs":"+ 另存为新",
|
|
134
134
|
"workshop.library.rename":"✎ 重命名",
|
|
135
|
+
// —— 0.8.0 人物关系图谱 / 群聊分组 ——
|
|
136
|
+
"workshop.group.all":"全部",
|
|
137
|
+
"workshop.group.none":"未分组",
|
|
138
|
+
"workshop.group.new":"+ 组",
|
|
139
|
+
"workshop.group.summary":"{n} 卡 / {m} 关系",
|
|
140
|
+
"workshop.group.prompt":"新分组名称(同一世界观的角色放一组)",
|
|
141
|
+
"workshop.group.assign":"组",
|
|
142
|
+
"workshop.group.set":"已归组",
|
|
143
|
+
"workshop.rel.title":"人物关系",
|
|
144
|
+
"workshop.rel.open":"关系",
|
|
145
|
+
"workshop.rel.pickGroup":"先在左侧选一个具体分组(「全部 / 未分组」下没有可编辑的关系)。",
|
|
146
|
+
"workshop.rel.noGroup":"未选分组",
|
|
147
|
+
"workshop.rel.members":"成员卡",
|
|
148
|
+
"workshop.rel.noMember":"(还没有卡入组 —— 用卡片行上的「组」按钮把同一世界观的卡拉进来)",
|
|
149
|
+
"workshop.rel.edges":"关系",
|
|
150
|
+
"workshop.rel.noEdge":"(还没有关系:用下面的表单加一条,或让模型用 cc_patch_relations 批量建)",
|
|
151
|
+
"workshop.rel.add":"添加关系",
|
|
152
|
+
"workshop.rel.addNode":"+ NPC",
|
|
153
|
+
"workshop.rel.nodePrompt":"NPC 名字(没有独立角色卡的人物,例如「柳长老」)",
|
|
154
|
+
"workshop.rel.from":"从",
|
|
155
|
+
"workshop.rel.to":"到",
|
|
156
|
+
"workshop.rel.strength":"强度",
|
|
157
|
+
"workshop.rel.direction":"方向",
|
|
158
|
+
"workshop.rel.dirMutual":"对等",
|
|
159
|
+
"workshop.rel.dirOneWay":"单向",
|
|
160
|
+
"workshop.rel.note":"备注",
|
|
161
|
+
"workshop.rel.check":"体检",
|
|
162
|
+
"workshop.rel.render":"渲染到当前卡",
|
|
163
|
+
"workshop.rel.rendered":"已把 {n} 条关系写进当前卡(记得「保存当前」才会落库)",
|
|
164
|
+
"workshop.rel.checkResult":"体检:{n} 个问题",
|
|
165
|
+
"workshop.rel.checkOk":"体检通过:没有结构性问题",
|
|
166
|
+
"workshop.rel.saved":"关系已更新(rev {rev})",
|
|
167
|
+
"workshop.rel.needLoad":"先在左侧「载入」一张卡:渲染要知道写进哪张卡",
|
|
168
|
+
"workshop.rel.hint":"关系只存在插件侧,不写进 CCv3 字段;「渲染到当前卡」会把它们写进 description 的【关系】段,让分享出去的卡自包含。",
|
|
169
|
+
"rel.type.family":"亲属",
|
|
170
|
+
"rel.type.romance":"恋慕",
|
|
171
|
+
"rel.type.mentor":"师徒",
|
|
172
|
+
"rel.type.friend":"挚友",
|
|
173
|
+
"rel.type.colleague":"同门",
|
|
174
|
+
"rel.type.rival":"竞争",
|
|
175
|
+
"rel.type.enemy":"敌对",
|
|
176
|
+
"rel.type.debt":"恩义",
|
|
177
|
+
"rel.type.superior":"上下级",
|
|
178
|
+
"rel.type.other":"其他",
|
|
135
179
|
"workshop.library.new":"+ 新建",
|
|
136
180
|
"workshop.library.searchPlaceholder":"搜索 名称/标签…",
|
|
137
181
|
"workshop.library.empty":"还没有已存角色\n去“世界观/角色细化”填好后点“保存当前”",
|
|
@@ -345,6 +389,50 @@ window.__ModuleLoader__.load({
|
|
|
345
389
|
"workshop.library.save":"★ Save Current",
|
|
346
390
|
"workshop.library.saveAs":"+ Save as New",
|
|
347
391
|
"workshop.library.rename":"✎ Rename",
|
|
392
|
+
// —— 0.8.0 relationship graph / group chat ——
|
|
393
|
+
"workshop.group.all":"All",
|
|
394
|
+
"workshop.group.none":"Ungrouped",
|
|
395
|
+
"workshop.group.new":"+ Group",
|
|
396
|
+
"workshop.group.summary":"{n} cards / {m} relations",
|
|
397
|
+
"workshop.group.prompt":"New group name (characters of one world go together)",
|
|
398
|
+
"workshop.group.assign":"Group",
|
|
399
|
+
"workshop.group.set":"Grouped",
|
|
400
|
+
"workshop.rel.title":"Relationships",
|
|
401
|
+
"workshop.rel.open":"Relations",
|
|
402
|
+
"workshop.rel.pickGroup":"Pick a specific group on the left (no editable relations under All / Ungrouped).",
|
|
403
|
+
"workshop.rel.noGroup":"No group",
|
|
404
|
+
"workshop.rel.members":"Member cards",
|
|
405
|
+
"workshop.rel.noMember":"(no cards yet — use the “Group” button on a card row to pull in cards of the same world)",
|
|
406
|
+
"workshop.rel.edges":"Relations",
|
|
407
|
+
"workshop.rel.noEdge":"(none yet: add one with the form below, or let the model build them with cc_patch_relations)",
|
|
408
|
+
"workshop.rel.add":"Add relation",
|
|
409
|
+
"workshop.rel.addNode":"+ NPC",
|
|
410
|
+
"workshop.rel.nodePrompt":"NPC name (someone without their own card, e.g. an elder)",
|
|
411
|
+
"workshop.rel.from":"From",
|
|
412
|
+
"workshop.rel.to":"To",
|
|
413
|
+
"workshop.rel.strength":"Strength",
|
|
414
|
+
"workshop.rel.direction":"Direction",
|
|
415
|
+
"workshop.rel.dirMutual":"Mutual",
|
|
416
|
+
"workshop.rel.dirOneWay":"One-way",
|
|
417
|
+
"workshop.rel.note":"Note",
|
|
418
|
+
"workshop.rel.check":"Check",
|
|
419
|
+
"workshop.rel.render":"Render into current card",
|
|
420
|
+
"workshop.rel.rendered":"Wrote {n} relations into the current card (remember to “Save current”)",
|
|
421
|
+
"workshop.rel.checkResult":"Check: {n} issue(s)",
|
|
422
|
+
"workshop.rel.checkOk":"Check passed: no structural issues",
|
|
423
|
+
"workshop.rel.saved":"Relations updated (rev {rev})",
|
|
424
|
+
"workshop.rel.needLoad":"Load a card on the left first: rendering needs to know which card to write into",
|
|
425
|
+
"workshop.rel.hint":"Relations live on the plugin side only and are never written into CCv3 fields; “Render into current card” writes them into the 【关系】 block of description so a shared card stays self-contained.",
|
|
426
|
+
"rel.type.family":"Family",
|
|
427
|
+
"rel.type.romance":"Romance",
|
|
428
|
+
"rel.type.mentor":"Mentor",
|
|
429
|
+
"rel.type.friend":"Close friend",
|
|
430
|
+
"rel.type.colleague":"Colleague",
|
|
431
|
+
"rel.type.rival":"Rival",
|
|
432
|
+
"rel.type.enemy":"Enemy",
|
|
433
|
+
"rel.type.debt":"Obligation",
|
|
434
|
+
"rel.type.superior":"Superior",
|
|
435
|
+
"rel.type.other":"Other",
|
|
348
436
|
"workshop.library.new":"+ New",
|
|
349
437
|
"workshop.library.searchPlaceholder":"Search name/tags…",
|
|
350
438
|
"workshop.library.empty":"No saved characters\nFill World/Character then ★ Save Current",
|
|
@@ -487,7 +575,7 @@ window.__ModuleLoader__.load({
|
|
|
487
575
|
var _saved = loadSettings();
|
|
488
576
|
// 兼容旧开关:poll=false 视为 0 秒
|
|
489
577
|
if(_saved.poll===false) _saved.pollInterval = 0;
|
|
490
|
-
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, currentLibraryId:null, editingWorld:null, editingWorldRevert:null, editingText:null, pollEnabled:_saved.poll, pollInterval:_saved.pollInterval, autoValidate:_saved.autoValidate, autoSaveOnImport:_saved.autoSaveOnImport };
|
|
578
|
+
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 };
|
|
491
579
|
var listeners=new Set();
|
|
492
580
|
function emit(){ listeners.forEach(function(fn){ try{fn();}catch(e){console.error(e);} }); }
|
|
493
581
|
function getSnapshot(){ return state; }
|
|
@@ -791,6 +879,121 @@ window.__ModuleLoader__.load({
|
|
|
791
879
|
});
|
|
792
880
|
}
|
|
793
881
|
function setLibraryQuery(v){ set({libraryQuery: String(v).slice(0,40)}); }
|
|
882
|
+
// ———————————————— 0.8.0 人物关系图谱 / 群聊分组 ————————————————
|
|
883
|
+
// 关系数据活在宿主侧(<DSH_HOME>/cc-groups/),前端只通过 RPC 读写;「卡属于哪个组」由库条目
|
|
884
|
+
// 的 meta.groupId 决定(分组文件里不存成员),所以这里每次入组/移组后都要 refreshLibrary。
|
|
885
|
+
function setCurrentGroupId(id){
|
|
886
|
+
var gid = String(id || "");
|
|
887
|
+
set({currentGroupId: gid});
|
|
888
|
+
// "" = 全部(不过滤)、"__none" = 只看未分组:这两个都不是真分组,没有图谱可载
|
|
889
|
+
if(gid && gid !== "__none") loadGroup(gid); else set({relations:null});
|
|
890
|
+
}
|
|
891
|
+
async function refreshGroups(){
|
|
892
|
+
try{
|
|
893
|
+
var res = await rpc("cc_listGroups", {});
|
|
894
|
+
if(res && res.ok && res.value) set({groups: res.value.groups||[]});
|
|
895
|
+
}catch(e){ set({lastMsg:String(e && e.message || e)}); }
|
|
896
|
+
}
|
|
897
|
+
async function loadGroup(id){
|
|
898
|
+
var gid = String(id || state.currentGroupId || "");
|
|
899
|
+
if(!gid){ set({relations:null}); return; }
|
|
900
|
+
set({relBusy:"load"});
|
|
901
|
+
try{
|
|
902
|
+
var res = await rpc("cc_getGroup", {id: gid});
|
|
903
|
+
if(res && res.ok && res.value) set({relations: res.value});
|
|
904
|
+
else set({relations:null, lastMsg:String((res && res.error && res.error.message) || "")});
|
|
905
|
+
}catch(e){ set({lastMsg:String(e && e.message || e)}); }
|
|
906
|
+
set({relBusy:null});
|
|
907
|
+
}
|
|
908
|
+
async function createGroup(){
|
|
909
|
+
var name = "";
|
|
910
|
+
try{ if(typeof window!=="undefined" && window.prompt) name = String(window.prompt(t("workshop.group.prompt"), "")||"").trim(); }catch(e){}
|
|
911
|
+
if(!name) return;
|
|
912
|
+
set({relBusy:"new"});
|
|
913
|
+
try{
|
|
914
|
+
var res = await rpc("cc_createGroup", {name: name});
|
|
915
|
+
if(res && res.ok && res.value){
|
|
916
|
+
await refreshGroups();
|
|
917
|
+
set({currentGroupId: res.value.group.id});
|
|
918
|
+
await loadGroup(res.value.group.id);
|
|
919
|
+
} else set({lastMsg:String((res && res.error && res.error.message) || "")});
|
|
920
|
+
}catch(e){ set({lastMsg:String(e && e.message || e)}); }
|
|
921
|
+
set({relBusy:null});
|
|
922
|
+
}
|
|
923
|
+
async function deleteGroup(id){
|
|
924
|
+
try{ if(typeof window!=="undefined" && window.confirm && !window.confirm(t("store.confirmDelete"))) return; }catch(e){}
|
|
925
|
+
set({relBusy:"del"});
|
|
926
|
+
try{
|
|
927
|
+
var res = await rpc("cc_deleteGroup", {id: id});
|
|
928
|
+
if(res && res.ok){ set({currentGroupId:"", relations:null}); await refreshGroups(); await refreshLibrary(); }
|
|
929
|
+
else set({lastMsg:String((res && res.error && res.error.message) || "")});
|
|
930
|
+
}catch(e){ set({lastMsg:String(e && e.message || e)}); }
|
|
931
|
+
set({relBusy:null});
|
|
932
|
+
}
|
|
933
|
+
async function assignGroup(cardId, groupId){
|
|
934
|
+
if(!cardId) return;
|
|
935
|
+
set({libraryBusy:"group:"+cardId});
|
|
936
|
+
try{
|
|
937
|
+
var res = await rpc("cc_setCardGroup", {cardId: cardId, groupId: groupId || null});
|
|
938
|
+
if(res && res.ok){
|
|
939
|
+
await refreshLibrary();
|
|
940
|
+
await refreshGroups();
|
|
941
|
+
if(state.currentGroupId) await loadGroup(state.currentGroupId);
|
|
942
|
+
} else set({lastMsg:String((res && res.error && res.error.message) || "")});
|
|
943
|
+
}catch(e){ set({lastMsg:String(e && e.message || e)}); }
|
|
944
|
+
set({libraryBusy:null});
|
|
945
|
+
}
|
|
946
|
+
/** 改图谱:ops 与 cc_patch_relations 同构(addNode/updateNode/removeNode/addEdge/updateEdge/removeEdge)。 */
|
|
947
|
+
async function patchRelations(ops){
|
|
948
|
+
var gid = state.currentGroupId;
|
|
949
|
+
if(!gid){ set({lastMsg:t("workshop.rel.pickGroup")}); return; }
|
|
950
|
+
set({relBusy:"patch"});
|
|
951
|
+
try{
|
|
952
|
+
var args = { id: gid, ops: ops };
|
|
953
|
+
var rev = state.relations && state.relations.group ? state.relations.group.rev : null;
|
|
954
|
+
if(typeof rev === "number") args.expectedRev = rev;
|
|
955
|
+
var res = await rpc("cc_patchGroup", args);
|
|
956
|
+
if(res && res.ok && res.value){
|
|
957
|
+
set({lastMsg: t("workshop.rel.saved", {rev: String(res.value.group.rev)})});
|
|
958
|
+
await loadGroup(gid);
|
|
959
|
+
} else set({lastMsg:String((res && res.error && res.error.message) || "")});
|
|
960
|
+
}catch(e){ set({lastMsg:String(e && e.message || e)}); }
|
|
961
|
+
set({relBusy:null});
|
|
962
|
+
}
|
|
963
|
+
async function checkGroupNow(){
|
|
964
|
+
var gid = state.currentGroupId;
|
|
965
|
+
if(!gid){ set({lastMsg:t("workshop.rel.pickGroup")}); return; }
|
|
966
|
+
set({relBusy:"check"});
|
|
967
|
+
try{
|
|
968
|
+
var res = await rpc("cc_groupCheck", {id: gid});
|
|
969
|
+
if(res && res.ok && res.value){
|
|
970
|
+
var issues = res.value.issues || [];
|
|
971
|
+
set({lastMsg: issues.length
|
|
972
|
+
? t("workshop.rel.checkResult", {n: String(issues.length)}) + " — " + issues.map(function(i){ return i.message; }).join(";")
|
|
973
|
+
: t("workshop.rel.checkOk")});
|
|
974
|
+
} else set({lastMsg:String((res && res.error && res.error.message) || "")});
|
|
975
|
+
}catch(e){ set({lastMsg:String(e && e.message || e)}); }
|
|
976
|
+
set({relBusy:null});
|
|
977
|
+
}
|
|
978
|
+
/** 把图谱里「我对别人的看法」渲染进当前卡的 description(固定标记区,重复渲染是替换)。 */
|
|
979
|
+
async function renderRelationsToCard(){
|
|
980
|
+
if(!state.currentLibraryId){ set({lastMsg:t("workshop.rel.needLoad")}); return; }
|
|
981
|
+
set({relBusy:"render"});
|
|
982
|
+
try{
|
|
983
|
+
var sid = state.currentSessionId || null;
|
|
984
|
+
var args = { cardId: state.currentLibraryId };
|
|
985
|
+
if(state.currentGroupId) args.groupId = state.currentGroupId;
|
|
986
|
+
if(sid) args.sessionId = sid;
|
|
987
|
+
var res = await rpc("cc_renderRelations", args);
|
|
988
|
+
if(res && res.ok && res.value){
|
|
989
|
+
// 宿主改了草稿文件 → 重新拉一次,否则工坊里看到的还是旧 description
|
|
990
|
+
var dres = await rpc("cc_getDraft", sid ? {sessionId: sid} : {});
|
|
991
|
+
if(dres && dres.ok && dres.value && dres.value.draft) setDraftFromHost(dres.value.draft, dres.value.draftStatus || null);
|
|
992
|
+
set({lastMsg: t("workshop.rel.rendered", {n: String(res.value.outgoing || 0)})});
|
|
993
|
+
} else set({lastMsg:String((res && res.error && res.error.message) || "")});
|
|
994
|
+
}catch(e){ set({lastMsg:String(e && e.message || e)}); }
|
|
995
|
+
set({relBusy:null});
|
|
996
|
+
}
|
|
794
997
|
function toggleLibraryCollapsed(){ set({libraryCollapsed: !state.libraryCollapsed}); }
|
|
795
998
|
async function refreshLibrary(){
|
|
796
999
|
set({libraryBusy:"refresh"});
|
|
@@ -919,7 +1122,7 @@ window.__ModuleLoader__.load({
|
|
|
919
1122
|
// 0.4.1:这里原本还有一个 exportLibraryEntry —— 直接下载某张已存角色的 JSON。
|
|
920
1123
|
// 三个导出入口归一之后,卡片上的「导出」改成切到导出页,导出动作只剩导出页那一套实现,
|
|
921
1124
|
// 这条旁路就没有调用者了(留着它等于「同一次导出有两套行为」),故删除。
|
|
922
|
-
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 };
|
|
1125
|
+
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 };
|
|
923
1126
|
}
|
|
924
1127
|
|
|
925
1128
|
var S={
|
|
@@ -1158,6 +1361,8 @@ window.__ModuleLoader__.load({
|
|
|
1158
1361
|
// 从「已存角色」卡片跳进导出页时的来源卡(几秒后自动清掉):用来给那枚按钮一个即时反馈,
|
|
1159
1362
|
// 并在导出页把「导出的仍是工坊里这张、你点的那张还没载入」说清楚。清空后回到常态提示。
|
|
1160
1363
|
var jumpRef=React.useState(null); var jumpFrom=jumpRef[0], setJumpFrom=jumpRef[1];
|
|
1364
|
+
// 0.8.0 关系视图的「添加关系」表单(局部状态,不进 store:它只是草稿输入)
|
|
1365
|
+
var relFormRef=React.useState({from:"", to:"", type:"friend", strength:3, direction:"one-way", note:""}); var relForm=relFormRef[0], setRelForm=relFormRef[1];
|
|
1161
1366
|
React.useEffect(function(){ return store.subscribe(function(){ setS(store.getSnapshot()); }); }, [store]);
|
|
1162
1367
|
React.useEffect(function(){
|
|
1163
1368
|
if(s.panelOpen) store.refreshLibrary();
|
|
@@ -1347,7 +1552,7 @@ window.__ModuleLoader__.load({
|
|
|
1347
1552
|
s.lastMsg? h("div",{style:{marginTop:8, fontSize:12, color:"var(--dsw-alias-label-secondary)"}}, s.lastMsg):null
|
|
1348
1553
|
)
|
|
1349
1554
|
);
|
|
1350
|
-
} else {
|
|
1555
|
+
} else if(s.cur===3){
|
|
1351
1556
|
var v=s.valid;
|
|
1352
1557
|
main=h("div",null,
|
|
1353
1558
|
h("div",{style:S.readCard},
|
|
@@ -1385,6 +1590,83 @@ window.__ModuleLoader__.load({
|
|
|
1385
1590
|
s.lastMsg? h("div",{style:{marginTop:8, fontSize:12, color:"var(--dsw-alias-label-secondary)"}}, s.lastMsg):null
|
|
1386
1591
|
)
|
|
1387
1592
|
);
|
|
1593
|
+
} else {
|
|
1594
|
+
// ── 关系视图(s.cur === 4)──
|
|
1595
|
+
// 与导出页同属「不在左侧导航里的视图」:入口是右侧分组选择器旁的「关系」按钮。
|
|
1596
|
+
// 这里编辑的是**插件侧图谱**(<DSH_HOME>/cc-groups/);只有「渲染到当前卡」才会把关系写进 description。
|
|
1597
|
+
var relNodes = (s.relations && s.relations.nodes) || [];
|
|
1598
|
+
var relEdges = (s.relations && s.relations.edges) || [];
|
|
1599
|
+
var relMembers = (s.relations && s.relations.members) || [];
|
|
1600
|
+
var relGroup = s.relations && s.relations.group;
|
|
1601
|
+
var gid = String(s.currentGroupId || "");
|
|
1602
|
+
var canEdit = !!gid && gid !== "__none";
|
|
1603
|
+
var relTypes = ["family","romance","mentor","friend","colleague","rival","enemy","debt","superior","other"];
|
|
1604
|
+
var nodeName = function(id){ var hit = relNodes.filter(function(x){ return x.id === id; })[0]; return hit ? hit.name : id; };
|
|
1605
|
+
var setF = function(patch){ setRelForm(Object.assign({}, relForm, patch)); };
|
|
1606
|
+
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"};
|
|
1607
|
+
main = h("div",null,
|
|
1608
|
+
h("div",{style:S.readCard},
|
|
1609
|
+
h("h3",{style:S.readTitle}, t("workshop.rel.title"),
|
|
1610
|
+
h("span",{style:{marginLeft:8, fontSize:12, fontWeight:400, color:"var(--dsw-alias-label-secondary)"}}, "· " + ((relGroup && relGroup.name) || t("workshop.rel.noGroup"))),
|
|
1611
|
+
relGroup ? h("span",{style:Object.assign({}, S.badge, {marginLeft:8})}, "rev " + relGroup.rev) : null),
|
|
1612
|
+
h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", marginBottom:10, lineHeight:1.7}}, t("workshop.rel.hint")),
|
|
1613
|
+
!canEdit ? h("div",{style:{fontSize:12, color:"var(--dsw-alias-label-secondary)"}}, t("workshop.rel.pickGroup")) : h("div",null,
|
|
1614
|
+
h("div",{style:{fontSize:12, fontWeight:600, marginBottom:4}}, t("workshop.rel.members") + " · " + relMembers.length),
|
|
1615
|
+
h("div",{style:{display:"flex", flexWrap:"wrap", gap:6, marginBottom:14, alignItems:"center"}},
|
|
1616
|
+
relMembers.map(function(m){ return h("span",{key:m.id, style:S.badge}, m.name); }),
|
|
1617
|
+
relMembers.length===0 ? h("span",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)"}}, t("workshop.rel.noMember")) : null),
|
|
1618
|
+
h("div",{style:{display:"flex", flexWrap:"wrap", gap:6, marginBottom:14, alignItems:"center"}},
|
|
1619
|
+
relNodes.filter(function(n){ return n.kind === "npc"; }).map(function(n){ return h("span",{key:n.id, style:S.badge}, n.name); }),
|
|
1620
|
+
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:"2px 8px", fontSize:11}), onClick:function(){
|
|
1621
|
+
var name = "";
|
|
1622
|
+
try{ if(typeof window!=="undefined" && window.prompt) name = String(window.prompt(t("workshop.rel.nodePrompt"), "")||"").trim(); }catch(e){}
|
|
1623
|
+
if(name) store.patchRelations([{op:"addNode", kind:"npc", name:name}]);
|
|
1624
|
+
}}, t("workshop.rel.addNode"))),
|
|
1625
|
+
h("div",{style:{fontSize:12, fontWeight:600, marginBottom:4}}, t("workshop.rel.edges") + " · " + relEdges.length),
|
|
1626
|
+
relEdges.length===0 ? h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", marginBottom:10}}, t("workshop.rel.noEdge")) :
|
|
1627
|
+
h("div",null, relEdges.map(function(e){
|
|
1628
|
+
return h("div",{key:e.id, style:{borderBottom:"1px solid var(--dsw-alias-border-l1)", padding:"6px 0"}},
|
|
1629
|
+
h("div",{style:{display:"flex", alignItems:"center", gap:6, flexWrap:"wrap"}},
|
|
1630
|
+
h("span",{style:{fontSize:12}}, nodeName(e.from)),
|
|
1631
|
+
h("span",{style:{color:"#7c5cff"}}, e.direction === "mutual" ? "⇄" : "→"),
|
|
1632
|
+
h("span",{style:{fontSize:12}}, nodeName(e.to)),
|
|
1633
|
+
h("select",{style:smallSel, value:e.type, onChange:function(ev){ store.patchRelations([{op:"updateEdge", edgeId:e.id, type:ev.target.value}]); }},
|
|
1634
|
+
relTypes.map(function(ty){ return h("option",{key:ty, value:ty}, t("rel.type."+ty)); })),
|
|
1635
|
+
h("select",{style:smallSel, value:String(e.strength), onChange:function(ev){ store.patchRelations([{op:"updateEdge", edgeId:e.id, strength:Number(ev.target.value)}]); }},
|
|
1636
|
+
[1,2,3,4,5].map(function(n){ return h("option",{key:n, value:String(n)}, n + "/5"); })),
|
|
1637
|
+
h("select",{style:smallSel, value:e.direction, onChange:function(ev){ store.patchRelations([{op:"updateEdge", edgeId:e.id, direction:ev.target.value}]); }},
|
|
1638
|
+
h("option",{value:"one-way"}, t("workshop.rel.dirOneWay")),
|
|
1639
|
+
h("option",{value:"mutual"}, t("workshop.rel.dirMutual"))),
|
|
1640
|
+
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}]); }}, "✕")),
|
|
1641
|
+
e.note ? h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", marginTop:2}}, e.note) : null);
|
|
1642
|
+
})),
|
|
1643
|
+
h("div",{style:{marginTop:14, paddingTop:10, borderTop:"1px dashed var(--dsw-alias-border-l1)"}},
|
|
1644
|
+
h("div",{style:{fontSize:12, fontWeight:600, marginBottom:6}}, t("workshop.rel.add")),
|
|
1645
|
+
h("div",{style:{display:"flex", gap:6, flexWrap:"wrap", alignItems:"center"}},
|
|
1646
|
+
h("span",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)"}}, t("workshop.rel.from")),
|
|
1647
|
+
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}); }},
|
|
1648
|
+
h("option",{value:""}, "—"),
|
|
1649
|
+
relNodes.map(function(n){ return h("option",{key:n.id, value:n.id}, n.name); })),
|
|
1650
|
+
h("span",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)"}}, t("workshop.rel.to")),
|
|
1651
|
+
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}); }},
|
|
1652
|
+
h("option",{value:""}, "—"),
|
|
1653
|
+
relNodes.map(function(n){ return h("option",{key:n.id, value:n.id}, n.name); })),
|
|
1654
|
+
h("select",{style:Object.assign({}, smallSel, {background:"var(--dsw-alias-bg-base)"}), value:relForm.type, onChange:function(ev){ setF({type:ev.target.value}); }},
|
|
1655
|
+
relTypes.map(function(ty){ return h("option",{key:ty, value:ty}, t("rel.type."+ty)); })),
|
|
1656
|
+
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)}); }},
|
|
1657
|
+
[1,2,3,4,5].map(function(n){ return h("option",{key:n, value:String(n)}, t("workshop.rel.strength") + " " + n); })),
|
|
1658
|
+
h("select",{style:Object.assign({}, smallSel, {background:"var(--dsw-alias-bg-base)"}), value:relForm.direction, onChange:function(ev){ setF({direction:ev.target.value}); }},
|
|
1659
|
+
h("option",{value:"one-way"}, t("workshop.rel.dirOneWay")),
|
|
1660
|
+
h("option",{value:"mutual"}, t("workshop.rel.dirMutual"))),
|
|
1661
|
+
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"))),
|
|
1662
|
+
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}); }})),
|
|
1663
|
+
h("div",{style:{display:"flex", gap:8, marginTop:14, flexWrap:"wrap"}},
|
|
1664
|
+
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")),
|
|
1665
|
+
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")),
|
|
1666
|
+
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")),
|
|
1667
|
+
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")))),
|
|
1668
|
+
s.lastMsg ? h("div",{style:{marginTop:10, fontSize:12, color:"var(--dsw-alias-label-secondary)", lineHeight:1.7}}, s.lastMsg) : null)
|
|
1669
|
+
);
|
|
1388
1670
|
}
|
|
1389
1671
|
var stepsEls=steps.map(function(label, i){
|
|
1390
1672
|
var active=s.cur===i;
|
|
@@ -1395,9 +1677,14 @@ window.__ModuleLoader__.load({
|
|
|
1395
1677
|
onClick:function(){ store.setCur(i); }}, (i+1)+". "+label);
|
|
1396
1678
|
});
|
|
1397
1679
|
var filteredLibrary = (function(){
|
|
1680
|
+
var gid = String(s.currentGroupId||"");
|
|
1681
|
+
var base = s.library;
|
|
1682
|
+
// 0.8.0:分组筛选("" = 全部;"__none" = 只看未分组)
|
|
1683
|
+
if(gid === "__none") base = base.filter(function(e){ return !e.groupId; });
|
|
1684
|
+
else if(gid) base = base.filter(function(e){ return String(e.groupId||"")===gid; });
|
|
1398
1685
|
var q = String(s.libraryQuery||"").trim().toLowerCase();
|
|
1399
|
-
if(!q) return
|
|
1400
|
-
return
|
|
1686
|
+
if(!q) return base;
|
|
1687
|
+
return base.filter(function(e){
|
|
1401
1688
|
var nameMatch = String(e.name||"").toLowerCase().indexOf(q)>=0;
|
|
1402
1689
|
var tagMatch = Array.isArray(e.tags) && e.tags.some(function(t){ return String(t).toLowerCase().indexOf(q)>=0; });
|
|
1403
1690
|
var loreMatch = String(e.loreCount||"").indexOf(q)>=0;
|
|
@@ -1443,6 +1730,14 @@ window.__ModuleLoader__.load({
|
|
|
1443
1730
|
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"))
|
|
1444
1731
|
),
|
|
1445
1732
|
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); }}),
|
|
1733
|
+
// 0.8.0 分组:同一世界观的角色拉进一组,关系网按组维护(「关系」按钮进关系视图,s.cur===4)
|
|
1734
|
+
h("div",{style:{display:"flex", gap:6, marginTop:6}},
|
|
1735
|
+
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); }},
|
|
1736
|
+
h("option",{value:""}, t("workshop.group.all")),
|
|
1737
|
+
h("option",{value:"__none"}, t("workshop.group.none")),
|
|
1738
|
+
(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)})); })),
|
|
1739
|
+
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(){ store.createGroup(); }}, t("workshop.group.new")),
|
|
1740
|
+
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}), disabled: !s.currentGroupId || s.currentGroupId==="__none", onClick:function(){ store.setCur(4); }}, t("workshop.rel.open"))),
|
|
1446
1741
|
s.lastMsg ? h("div",{style:{fontSize:11, color:"var(--dsw-alias-label-secondary)", whiteSpace:"nowrap", overflow:"hidden", textOverflow:"ellipsis"}}, s.lastMsg) : null
|
|
1447
1742
|
),
|
|
1448
1743
|
h("div",{style:S.libraryList},
|
|
@@ -1473,7 +1768,11 @@ window.__ModuleLoader__.load({
|
|
|
1473
1768
|
// 想导这张已存角色先点「载入」—— 免得这里悄悄换掉用户正在编辑的草稿。
|
|
1474
1769
|
// 点下去按钮自己先变(「已切到 ✓」),不然切页发生在中栏、用户视线却在右侧,会以为没反应。
|
|
1475
1770
|
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")),
|
|
1476
|
-
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"))
|
|
1771
|
+
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")),
|
|
1772
|
+
// 0.8.0:直接把卡归到某个分组(空 = 未分组)。分组是关系图谱的载体。
|
|
1773
|
+
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); }},
|
|
1774
|
+
h("option",{value:""}, t("workshop.group.none")),
|
|
1775
|
+
(s.groups||[]).map(function(g){ return h("option",{key:g.id, value:g.id}, g.name); }))
|
|
1477
1776
|
)
|
|
1478
1777
|
);
|
|
1479
1778
|
})
|