@musnows/scriverse 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +24 -2
- package/dist/app.js.map +1 -1
- package/dist/database.js +159 -2
- package/dist/database.js.map +1 -1
- package/dist/public/app.js +281 -21
- package/dist/public/index.html +6 -2
- package/dist/public/styles.css +43 -5
- package/dist/store.js +238 -60
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +12 -10
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/public/app.js
CHANGED
|
@@ -1052,9 +1052,14 @@ function renderPresence() {
|
|
|
1052
1052
|
return;
|
|
1053
1053
|
}
|
|
1054
1054
|
const groups = groupedPresenceParticipants();
|
|
1055
|
+
const showControl = groups.length > 1;
|
|
1055
1056
|
const localKey = presencePageKey(presencePageForRoute());
|
|
1056
1057
|
syncChapterAutoSaveWithPresence();
|
|
1057
|
-
control.classList.
|
|
1058
|
+
control.classList.toggle("hidden", !showControl);
|
|
1059
|
+
if (!showControl) {
|
|
1060
|
+
$("#presence-panel").classList.add("hidden");
|
|
1061
|
+
$("#presence-button").setAttribute("aria-expanded", "false");
|
|
1062
|
+
}
|
|
1058
1063
|
$("#presence-count").textContent = `${groups.length} 人`;
|
|
1059
1064
|
$("#presence-list").innerHTML = groups.map((participant) => {
|
|
1060
1065
|
const isCurrent = participant.userId === state.user?.userId;
|
|
@@ -3445,15 +3450,18 @@ async function createNewAiConversation(taskType = "chat") {
|
|
|
3445
3450
|
function favoriteCharactersFirst(characters) {
|
|
3446
3451
|
const items = Array.isArray(characters) ? characters : [];
|
|
3447
3452
|
return [
|
|
3448
|
-
...items.filter((character) => character.isFavorite === true),
|
|
3449
|
-
...items.filter((character) => character.isFavorite !== true)
|
|
3453
|
+
...items.filter((character) => character.isPinned === true && character.isFavorite === true),
|
|
3454
|
+
...items.filter((character) => character.isPinned === true && character.isFavorite !== true),
|
|
3455
|
+
...items.filter((character) => character.isPinned !== true && character.isFavorite === true),
|
|
3456
|
+
...items.filter((character) => character.isPinned !== true && character.isFavorite !== true)
|
|
3450
3457
|
];
|
|
3451
3458
|
}
|
|
3452
3459
|
|
|
3453
3460
|
function roleplayCharacterOptionLabel(character) {
|
|
3454
|
-
const
|
|
3461
|
+
const pinLabel = character?.isPinned === true ? "[置顶]" : "";
|
|
3462
|
+
const favoriteLabel = character?.isFavorite === true ? "[收藏]" : "";
|
|
3455
3463
|
const deathLabel = character?.isDead ? "(已死亡)" : "";
|
|
3456
|
-
return `${favoriteLabel}${String(character?.name ?? "")}${deathLabel}`;
|
|
3464
|
+
return `${pinLabel}${favoriteLabel}${pinLabel || favoriteLabel ? " " : ""}${String(character?.name ?? "")}${deathLabel}`;
|
|
3457
3465
|
}
|
|
3458
3466
|
|
|
3459
3467
|
function renderAiRoleplayCharacterSelect() {
|
|
@@ -4109,6 +4117,31 @@ function aiPromptTextBeforeCursor() {
|
|
|
4109
4117
|
return promptTextFromNode(fragment).replace(/\n$/u, "");
|
|
4110
4118
|
}
|
|
4111
4119
|
|
|
4120
|
+
function aiPromptTextFromRange(range, prompt) {
|
|
4121
|
+
const beforeCursor = range.cloneRange();
|
|
4122
|
+
beforeCursor.selectNodeContents(prompt);
|
|
4123
|
+
beforeCursor.setEnd(range.endContainer, range.endOffset);
|
|
4124
|
+
const fragment = document.createElement("div");
|
|
4125
|
+
fragment.append(beforeCursor.cloneContents());
|
|
4126
|
+
return promptTextFromNode(fragment).replace(/\n$/u, "");
|
|
4127
|
+
}
|
|
4128
|
+
|
|
4129
|
+
function aiPromptTextBoundary(prompt, offset) {
|
|
4130
|
+
const walker = document.createTreeWalker(prompt, NodeFilter.SHOW_TEXT);
|
|
4131
|
+
let remaining = Math.max(0, Number(offset) || 0);
|
|
4132
|
+
let lastTextNode = null;
|
|
4133
|
+
while (walker.nextNode()) {
|
|
4134
|
+
const textNode = walker.currentNode;
|
|
4135
|
+
if (textNode.parentElement?.closest("[data-ai-reference-key]")) continue;
|
|
4136
|
+
lastTextNode = textNode;
|
|
4137
|
+
const length = textNode.textContent?.length ?? 0;
|
|
4138
|
+
if (remaining <= length) return { node: textNode, offset: remaining };
|
|
4139
|
+
remaining -= length;
|
|
4140
|
+
}
|
|
4141
|
+
if (lastTextNode) return { node: lastTextNode, offset: lastTextNode.textContent?.length ?? 0 };
|
|
4142
|
+
return { node: prompt, offset: prompt.childNodes.length };
|
|
4143
|
+
}
|
|
4144
|
+
|
|
4112
4145
|
function hideAiMentionMenu() {
|
|
4113
4146
|
aiMentionMatch = null;
|
|
4114
4147
|
aiMentionRange = null;
|
|
@@ -4197,12 +4230,14 @@ function selectAiMention(button) {
|
|
|
4197
4230
|
id: button.dataset.aiReferenceId,
|
|
4198
4231
|
name: button.dataset.aiReferenceName
|
|
4199
4232
|
};
|
|
4200
|
-
const
|
|
4201
|
-
const
|
|
4202
|
-
const localText = textNode.nodeType === Node.TEXT_NODE ? textNode.textContent?.slice(0, range.startOffset) ?? "" : "";
|
|
4203
|
-
const localMention = findAiMention(localText);
|
|
4233
|
+
const cursorText = aiPromptTextFromRange(aiMentionRange, prompt);
|
|
4234
|
+
const localMention = findAiMention(cursorText);
|
|
4204
4235
|
if (!localMention) return hideAiMentionMenu();
|
|
4205
|
-
range.
|
|
4236
|
+
const range = document.createRange();
|
|
4237
|
+
const startBoundary = aiPromptTextBoundary(prompt, localMention.start);
|
|
4238
|
+
const endBoundary = aiPromptTextBoundary(prompt, cursorText.length);
|
|
4239
|
+
range.setStart(startBoundary.node, startBoundary.offset);
|
|
4240
|
+
range.setEnd(endBoundary.node, endBoundary.offset);
|
|
4206
4241
|
range.deleteContents();
|
|
4207
4242
|
const spacer = document.createTextNode(" ");
|
|
4208
4243
|
range.insertNode(spacer);
|
|
@@ -4705,6 +4740,7 @@ function uploadWithProgress(path, options = {}, onProgress = () => {}) {
|
|
|
4705
4740
|
request.upload.addEventListener("progress", (event) => {
|
|
4706
4741
|
onProgress(event.lengthComputable && event.total > 0 ? (event.loaded / event.total) * 100 : null);
|
|
4707
4742
|
});
|
|
4743
|
+
request.upload.addEventListener("load", () => onProgress(100));
|
|
4708
4744
|
request.addEventListener("error", () => {
|
|
4709
4745
|
updateSystemHealth({ status: "offline" });
|
|
4710
4746
|
reject(new Error("网络连接失败"));
|
|
@@ -5365,6 +5401,56 @@ function persistentToast(message, type = "info") {
|
|
|
5365
5401
|
return () => dismissToastElement(element);
|
|
5366
5402
|
}
|
|
5367
5403
|
|
|
5404
|
+
function createImportProgressToast(fileName) {
|
|
5405
|
+
const region = $("#import-progress-region");
|
|
5406
|
+
const element = document.createElement("div");
|
|
5407
|
+
element.className = "toast import-progress-toast";
|
|
5408
|
+
element.setAttribute("role", "status");
|
|
5409
|
+
element.setAttribute("aria-atomic", "true");
|
|
5410
|
+
const copy = document.createElement("div");
|
|
5411
|
+
copy.className = "import-progress-copy";
|
|
5412
|
+
const title = document.createElement("strong");
|
|
5413
|
+
title.textContent = `正在导入“${fileName}”`;
|
|
5414
|
+
const status = document.createElement("span");
|
|
5415
|
+
status.className = "import-progress-status";
|
|
5416
|
+
status.textContent = "正在上传 · 0%";
|
|
5417
|
+
const progress = document.createElement("progress");
|
|
5418
|
+
progress.max = 100;
|
|
5419
|
+
progress.value = 0;
|
|
5420
|
+
progress.setAttribute("aria-label", "书籍导入上传进度");
|
|
5421
|
+
copy.append(title, status);
|
|
5422
|
+
element.append(copy, progress);
|
|
5423
|
+
region.append(element);
|
|
5424
|
+
if (typeof region.showPopover === "function" && !region.matches(":popover-open")) region.showPopover();
|
|
5425
|
+
let closed = false;
|
|
5426
|
+
return {
|
|
5427
|
+
update(uploadProgress) {
|
|
5428
|
+
if (closed) return;
|
|
5429
|
+
if (Number.isFinite(uploadProgress) && uploadProgress >= 100) {
|
|
5430
|
+
progress.removeAttribute("value");
|
|
5431
|
+
status.textContent = "上传完成,正在解析并写入作品…";
|
|
5432
|
+
return;
|
|
5433
|
+
}
|
|
5434
|
+
if (Number.isFinite(uploadProgress)) {
|
|
5435
|
+
const percentage = Math.max(0, Math.min(99, Math.round(uploadProgress)));
|
|
5436
|
+
progress.value = percentage;
|
|
5437
|
+
status.textContent = `正在上传 · ${percentage}%`;
|
|
5438
|
+
} else {
|
|
5439
|
+
progress.removeAttribute("value");
|
|
5440
|
+
status.textContent = "正在上传…";
|
|
5441
|
+
}
|
|
5442
|
+
},
|
|
5443
|
+
close() {
|
|
5444
|
+
if (closed) return;
|
|
5445
|
+
closed = true;
|
|
5446
|
+
element.remove();
|
|
5447
|
+
if (!region.childElementCount && typeof region.hidePopover === "function" && region.matches(":popover-open")) {
|
|
5448
|
+
region.hidePopover();
|
|
5449
|
+
}
|
|
5450
|
+
}
|
|
5451
|
+
};
|
|
5452
|
+
}
|
|
5453
|
+
|
|
5368
5454
|
function restoreToastFocus(previousFocus) {
|
|
5369
5455
|
if (
|
|
5370
5456
|
previousFocus instanceof HTMLElement
|
|
@@ -8687,6 +8773,18 @@ function characterFavoriteIconMarkup() {
|
|
|
8687
8773
|
return '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m12 3 2.8 5.7 6.2.9-4.5 4.4 1.1 6.2-5.6-2.9-5.6 2.9 1.1-6.2L3 9.6l6.2-.9L12 3Z"></path></svg>';
|
|
8688
8774
|
}
|
|
8689
8775
|
|
|
8776
|
+
function pushPinIconMarkup() {
|
|
8777
|
+
return '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M16 9V4h1V2H7v2h1v5c0 1.66-1.34 3-3 3v2h5.97v6l1.03 1 1.03-1v-6H20v-2c-2.21 0-4-1.79-4-4Z"></path></svg>';
|
|
8778
|
+
}
|
|
8779
|
+
|
|
8780
|
+
function characterPinButton(item) {
|
|
8781
|
+
const isPinned = item.isPinned === true;
|
|
8782
|
+
const canPin = canEditModule("characters");
|
|
8783
|
+
const action = isPinned ? "取消置顶" : "置顶";
|
|
8784
|
+
const title = canPin ? action : "当前账户没有角色模块写入权限";
|
|
8785
|
+
return `<button class="character-pin-button${isPinned ? " is-pinned" : ""}" type="button" data-character-pin="${esc(item.id)}" aria-label="${action}角色“${esc(item.name)}”" aria-pressed="${isPinned}" title="${title}" ${canPin ? "" : "disabled"}>${pushPinIconMarkup()}</button>`;
|
|
8786
|
+
}
|
|
8787
|
+
|
|
8690
8788
|
function characterFavoriteButton(item) {
|
|
8691
8789
|
const isFavorite = item.isFavorite === true;
|
|
8692
8790
|
const canFavorite = canEditModule("characters");
|
|
@@ -8713,6 +8811,17 @@ function recordFavoriteButton(type, item, { cardControl = false } = {}) {
|
|
|
8713
8811
|
return `<button class="record-favorite-button${cardControl ? " is-card-control" : ""}${isFavorite ? " is-favorite" : ""}" type="button" data-record-favorite="${esc(item.id)}" data-record-favorite-type="${esc(type)}" aria-label="${action}${config.label}“${esc(name)}”" aria-pressed="${isFavorite}" title="${title}" ${canFavorite ? "" : "disabled"}>${characterFavoriteIconMarkup()}</button>`;
|
|
8714
8812
|
}
|
|
8715
8813
|
|
|
8814
|
+
function recordPinButton(type, item, { cardControl = false } = {}) {
|
|
8815
|
+
const config = recordFavoriteConfigs[type];
|
|
8816
|
+
if (!config) return "";
|
|
8817
|
+
const isPinned = item.isPinned === true;
|
|
8818
|
+
const canPin = canEditModule(config.module);
|
|
8819
|
+
const action = isPinned ? "取消置顶" : "置顶";
|
|
8820
|
+
const title = canPin ? action : `当前账户没有${config.label}模块写入权限`;
|
|
8821
|
+
const name = String(item[config.nameField] ?? "");
|
|
8822
|
+
return `<button class="record-pin-button${cardControl ? " is-card-control" : ""}${isPinned ? " is-pinned" : ""}" type="button" data-record-pin="${esc(item.id)}" data-record-pin-type="${esc(type)}" aria-label="${action}${config.label}“${esc(name)}”" aria-pressed="${isPinned}" title="${title}" ${canPin ? "" : "disabled"}>${pushPinIconMarkup()}</button>`;
|
|
8823
|
+
}
|
|
8824
|
+
|
|
8716
8825
|
async function toggleRecordFavorite(type, item, render) {
|
|
8717
8826
|
const config = recordFavoriteConfigs[type];
|
|
8718
8827
|
if (!config) return;
|
|
@@ -8729,6 +8838,22 @@ async function toggleRecordFavorite(type, item, render) {
|
|
|
8729
8838
|
toast(updated.isFavorite ? `已收藏${config.label}“${name}”` : `已取消收藏${config.label}“${name}”`);
|
|
8730
8839
|
}
|
|
8731
8840
|
|
|
8841
|
+
async function toggleRecordPin(type, item, render) {
|
|
8842
|
+
const config = recordFavoriteConfigs[type];
|
|
8843
|
+
if (!config) return;
|
|
8844
|
+
const updated = await api(`/api/${config.resource}/${encodeURIComponent(item.id)}/pin`, {
|
|
8845
|
+
method: "PATCH",
|
|
8846
|
+
body: { isPinned: item.isPinned !== true }
|
|
8847
|
+
});
|
|
8848
|
+
await render();
|
|
8849
|
+
const pinButton = [...$("#module-content").querySelectorAll("[data-record-pin]")].find((button) => (
|
|
8850
|
+
button.dataset.recordPinType === type && button.dataset.recordPin === String(updated.id)
|
|
8851
|
+
));
|
|
8852
|
+
pinButton?.focus({ preventScroll: true });
|
|
8853
|
+
const name = String(updated[config.nameField] ?? "");
|
|
8854
|
+
toast(updated.isPinned ? `已置顶${config.label}“${name}”` : `已取消置顶${config.label}“${name}”`);
|
|
8855
|
+
}
|
|
8856
|
+
|
|
8732
8857
|
function bindRecordFavoriteButtons(type, items, render) {
|
|
8733
8858
|
const config = recordFavoriteConfigs[type];
|
|
8734
8859
|
if (!config) return;
|
|
@@ -8745,6 +8870,22 @@ function bindRecordFavoriteButtons(type, items, render) {
|
|
|
8745
8870
|
}));
|
|
8746
8871
|
}
|
|
8747
8872
|
|
|
8873
|
+
function bindRecordPinButtons(type, items, render) {
|
|
8874
|
+
const config = recordFavoriteConfigs[type];
|
|
8875
|
+
if (!config) return;
|
|
8876
|
+
$("#module-content").querySelectorAll(`[data-record-pin-type="${type}"]`).forEach((button) => button.addEventListener("click", async () => {
|
|
8877
|
+
const item = items.find((candidate) => candidate.id === button.dataset.recordPin);
|
|
8878
|
+
if (!item || !canEditModule(config.module)) return;
|
|
8879
|
+
button.disabled = true;
|
|
8880
|
+
try {
|
|
8881
|
+
await toggleRecordPin(type, item, render);
|
|
8882
|
+
} catch (error) {
|
|
8883
|
+
button.disabled = false;
|
|
8884
|
+
toast(`${config.label}置顶状态更新失败:${error.message}`, "error");
|
|
8885
|
+
}
|
|
8886
|
+
}));
|
|
8887
|
+
}
|
|
8888
|
+
|
|
8748
8889
|
function syncEntityDetailFavoriteButton(button, type, item) {
|
|
8749
8890
|
const config = recordFavoriteConfigs[type];
|
|
8750
8891
|
const visible = Boolean(config && item);
|
|
@@ -8769,6 +8910,30 @@ function syncEntityDetailFavoriteButton(button, type, item) {
|
|
|
8769
8910
|
button.disabled = !canFavorite;
|
|
8770
8911
|
}
|
|
8771
8912
|
|
|
8913
|
+
function syncEntityDetailPinButton(button, type, item) {
|
|
8914
|
+
const config = recordFavoriteConfigs[type];
|
|
8915
|
+
const visible = Boolean(config && item);
|
|
8916
|
+
button.classList.toggle("hidden", !visible);
|
|
8917
|
+
button.innerHTML = pushPinIconMarkup();
|
|
8918
|
+
if (!visible) {
|
|
8919
|
+
button.disabled = true;
|
|
8920
|
+
button.classList.remove("is-pinned");
|
|
8921
|
+
button.setAttribute("aria-pressed", "false");
|
|
8922
|
+
button.removeAttribute("aria-label");
|
|
8923
|
+
button.removeAttribute("title");
|
|
8924
|
+
return;
|
|
8925
|
+
}
|
|
8926
|
+
const isPinned = item.isPinned === true;
|
|
8927
|
+
const canPin = canEditModule(config.module);
|
|
8928
|
+
const action = isPinned ? "取消置顶" : "置顶";
|
|
8929
|
+
const name = String(item[config.nameField] ?? "");
|
|
8930
|
+
button.classList.toggle("is-pinned", isPinned);
|
|
8931
|
+
button.setAttribute("aria-pressed", String(isPinned));
|
|
8932
|
+
button.setAttribute("aria-label", `${action}${config.label}“${name}”`);
|
|
8933
|
+
button.title = canPin ? action : `当前账户没有${config.label}模块写入权限`;
|
|
8934
|
+
button.disabled = !canPin;
|
|
8935
|
+
}
|
|
8936
|
+
|
|
8772
8937
|
function bindEntityDetailFavoriteButton(button, type, getItem, onUpdated) {
|
|
8773
8938
|
syncEntityDetailFavoriteButton(button, type, getItem());
|
|
8774
8939
|
button.onclick = async () => {
|
|
@@ -8793,6 +8958,30 @@ function bindEntityDetailFavoriteButton(button, type, getItem, onUpdated) {
|
|
|
8793
8958
|
};
|
|
8794
8959
|
}
|
|
8795
8960
|
|
|
8961
|
+
function bindEntityDetailPinButton(button, type, getItem, onUpdated) {
|
|
8962
|
+
syncEntityDetailPinButton(button, type, getItem());
|
|
8963
|
+
button.onclick = async () => {
|
|
8964
|
+
const config = recordFavoriteConfigs[type];
|
|
8965
|
+
const item = getItem();
|
|
8966
|
+
if (!config || !item || !canEditModule(config.module)) return;
|
|
8967
|
+
button.disabled = true;
|
|
8968
|
+
try {
|
|
8969
|
+
const updated = await api(`/api/${config.resource}/${encodeURIComponent(item.id)}/pin`, {
|
|
8970
|
+
method: "PATCH",
|
|
8971
|
+
body: { isPinned: item.isPinned !== true }
|
|
8972
|
+
});
|
|
8973
|
+
onUpdated(updated);
|
|
8974
|
+
syncEntityDetailPinButton(button, type, getItem() ?? updated);
|
|
8975
|
+
button.focus({ preventScroll: true });
|
|
8976
|
+
const name = String(updated[config.nameField] ?? "");
|
|
8977
|
+
toast(updated.isPinned ? `已置顶${config.label}“${name}”` : `已取消置顶${config.label}“${name}”`);
|
|
8978
|
+
} catch (error) {
|
|
8979
|
+
syncEntityDetailPinButton(button, type, getItem());
|
|
8980
|
+
toast(`${config.label}置顶状态更新失败:${error instanceof Error ? error.message : "未知错误"}`, "error");
|
|
8981
|
+
}
|
|
8982
|
+
};
|
|
8983
|
+
}
|
|
8984
|
+
|
|
8796
8985
|
function recordCardEditButton(attribute, id, label) {
|
|
8797
8986
|
return `<button class="record-card-edit" type="button" data-${attribute}="${esc(id)}" aria-label="编辑${esc(label)}" title="编辑">${pencilIconMarkup()}</button>`;
|
|
8798
8987
|
}
|
|
@@ -9131,7 +9320,7 @@ function settingRecordActions(item) {
|
|
|
9131
9320
|
const recordAction = canEditModule("settings")
|
|
9132
9321
|
? recordCardEditButton("edit-setting", item.id, `设定“${item.title}”`)
|
|
9133
9322
|
: recordHistoryButton("setting", item.id, item.title);
|
|
9134
|
-
return `${recordFavoriteButton("setting", item)}${recordAction}`;
|
|
9323
|
+
return `${recordPinButton("setting", item)}${recordFavoriteButton("setting", item)}${recordAction}`;
|
|
9135
9324
|
}
|
|
9136
9325
|
|
|
9137
9326
|
function draftTypeLabel(draftType) {
|
|
@@ -9245,7 +9434,11 @@ function openDraftDialog(item = null, { readOnly = false } = {}) {
|
|
|
9245
9434
|
}
|
|
9246
9435
|
if (item && viewOnly) {
|
|
9247
9436
|
const headerActions = $("#dialog-context-actions");
|
|
9248
|
-
headerActions.innerHTML = `<button id="draft-dialog-favorite" class="entity-detail-favorite-button" type="button" aria-pressed="false"></button>${canEditModule("drafts") ? '<button id="draft-dialog-edit" class="ghost-button" type="button">编辑想法</button>' : ""}`;
|
|
9437
|
+
headerActions.innerHTML = `<button id="draft-dialog-pin" class="entity-detail-pin-button hidden" type="button" aria-pressed="false"></button><button id="draft-dialog-favorite" class="entity-detail-favorite-button" type="button" aria-pressed="false"></button>${canEditModule("drafts") ? '<button id="draft-dialog-edit" class="ghost-button" type="button">编辑想法</button>' : ""}`;
|
|
9438
|
+
bindEntityDetailPinButton($("#draft-dialog-pin"), "draft", () => draftDialogItem, (updated) => {
|
|
9439
|
+
draftDialogItem = updated;
|
|
9440
|
+
void renderDrafts(moduleListPages.drafts).catch((error) => toast(`想法列表刷新失败:${error instanceof Error ? error.message : "未知错误"}`, "error"));
|
|
9441
|
+
});
|
|
9249
9442
|
bindEntityDetailFavoriteButton($("#draft-dialog-favorite"), "draft", () => draftDialogItem, (updated) => {
|
|
9250
9443
|
draftDialogItem = updated;
|
|
9251
9444
|
void renderDrafts(moduleListPages.drafts).catch((error) => toast(`想法列表刷新失败:${error instanceof Error ? error.message : "未知错误"}`, "error"));
|
|
@@ -9288,7 +9481,7 @@ async function renderDrafts(page = moduleListPages.drafts) {
|
|
|
9288
9481
|
<details class="character-filter-dropdown"><summary><span>按绑定位置筛选</span><strong>${selectedBindingKeys.size ? `已选 ${selectedBindingKeys.size} 项` : "全部位置"}</strong></summary><div id="draft-binding-filter" class="character-filter-options">${bindingOptions.map(([value, label]) => `<label class="character-filter-option"><input type="checkbox" value="${esc(value)}" ${selectedBindingKeys.has(value) ? "checked" : ""}><span>${esc(label)}</span></label>`).join("")}</div></details>
|
|
9289
9482
|
<div class="character-filter-toolbar-actions">${hasDraftFilters ? `<span class="character-filter-result-count" aria-live="polite">筛选后剩余 ${drafts.length} 条想法</span>` : ""}<button id="clear-draft-filters" class="ghost-button" type="button" ${hasDraftFilters ? "" : "disabled"}>重置筛选</button></div>
|
|
9290
9483
|
</section>`;
|
|
9291
|
-
const actions = (item) => `${recordFavoriteButton("draft", item)}${canEditModule("drafts")
|
|
9484
|
+
const actions = (item) => `${recordPinButton("draft", item)}${recordFavoriteButton("draft", item)}${canEditModule("drafts")
|
|
9292
9485
|
? `${recordCardEditButton("edit-draft", item.id, `想法“${item.title}”`)}${recordHistoryButton("draft", item.id, item.title)}`
|
|
9293
9486
|
: recordHistoryButton("draft", item.id, item.title)}`;
|
|
9294
9487
|
const cards = `<div class="card-grid">${pageResult.items.map((item) => `
|
|
@@ -9350,6 +9543,10 @@ async function renderDrafts(page = moduleListPages.drafts) {
|
|
|
9350
9543
|
moduleListPages.drafts = 1;
|
|
9351
9544
|
await renderDrafts(1);
|
|
9352
9545
|
});
|
|
9546
|
+
bindRecordPinButtons("draft", pageResult.items, async () => {
|
|
9547
|
+
moduleListPages.drafts = 1;
|
|
9548
|
+
await renderDrafts(1);
|
|
9549
|
+
});
|
|
9353
9550
|
bindEntityHistoryButtons(() => renderDrafts(pageResult.page));
|
|
9354
9551
|
}
|
|
9355
9552
|
|
|
@@ -9417,6 +9614,10 @@ async function renderSettings(page = moduleListPages.settings) {
|
|
|
9417
9614
|
moduleListPages.settings = 1;
|
|
9418
9615
|
await renderSettings(1);
|
|
9419
9616
|
});
|
|
9617
|
+
bindRecordPinButtons("setting", pageResult.items, async () => {
|
|
9618
|
+
moduleListPages.settings = 1;
|
|
9619
|
+
await renderSettings(1);
|
|
9620
|
+
});
|
|
9420
9621
|
bindEntityHistoryButtons(async () => { await renderSettings(pageResult.page); await loadAiReferences(); });
|
|
9421
9622
|
};
|
|
9422
9623
|
|
|
@@ -9468,6 +9669,21 @@ async function toggleCharacterFavorite(item) {
|
|
|
9468
9669
|
toast(updated.isFavorite ? `已收藏角色“${updated.name}”` : `已取消收藏角色“${updated.name}”`);
|
|
9469
9670
|
}
|
|
9470
9671
|
|
|
9672
|
+
async function toggleCharacterPin(item) {
|
|
9673
|
+
const updated = await api(`/api/characters/${encodeURIComponent(item.id)}/pin`, {
|
|
9674
|
+
method: "PATCH",
|
|
9675
|
+
body: { isPinned: item.isPinned !== true }
|
|
9676
|
+
});
|
|
9677
|
+
if (loadedAiReferencesWorkId === state.work?.id) {
|
|
9678
|
+
state.characters = upsertEntityCollection(state.characters, updated);
|
|
9679
|
+
renderAiRoleplayCharacterSelect();
|
|
9680
|
+
}
|
|
9681
|
+
characterListPage = 1;
|
|
9682
|
+
await renderCharacters(1);
|
|
9683
|
+
$("#module-content").querySelector(`[data-character-pin="${CSS.escape(String(updated.id))}"]`)?.focus({ preventScroll: true });
|
|
9684
|
+
toast(updated.isPinned ? `已置顶角色“${updated.name}”` : `已取消置顶角色“${updated.name}”`);
|
|
9685
|
+
}
|
|
9686
|
+
|
|
9471
9687
|
async function renderCharacters(page = characterListPage) {
|
|
9472
9688
|
const hasCharacterFilters = characterFilters.raceIds.length > 0
|
|
9473
9689
|
|| characterFilters.organizationIds.length > 0
|
|
@@ -9490,14 +9706,14 @@ async function renderCharacters(page = characterListPage) {
|
|
|
9490
9706
|
[state.races, state.organizations] = [races, organizations];
|
|
9491
9707
|
mountModuleCount(characterPage.total);
|
|
9492
9708
|
const layout = readModuleLayout();
|
|
9493
|
-
const characterActions = (item) => `${characterFavoriteButton(item)}${recordCardEditButton("edit-character", item.id, `角色“${item.name}”`)}`;
|
|
9709
|
+
const characterActions = (item) => `${characterPinButton(item)}${characterFavoriteButton(item)}${recordCardEditButton("edit-character", item.id, `角色“${item.name}”`)}`;
|
|
9494
9710
|
const characterLockBadge = (item) => item.lockedFields.length
|
|
9495
9711
|
? `<span class="character-lock-badge" aria-label="${item.lockedFields.length} 个锁定字段" title="锁定字段:${esc(item.lockedFields.join("、"))}"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="5" y="10" width="14" height="10" rx="2"></rect><path d="M8 10V7a4 4 0 0 1 8 0v3"></path></svg><span>${item.lockedFields.length}</span></span>`
|
|
9496
9712
|
: "";
|
|
9497
9713
|
const characterCards = () => `<div class="card-grid">${pageCharacters.map((item) => {
|
|
9498
9714
|
const details = normalizeCharacterDetails(item.attributes?.details);
|
|
9499
9715
|
return `
|
|
9500
|
-
<article class="record-card character-card preview-record-card has-card-edit" data-open-character="${esc(item.id)}" role="button" tabindex="0" aria-label="查看角色 ${esc(item.name)}">${characterFavoriteButton(item)}${recordCardEditButton("edit-character", item.id, `角色“${item.name}”`)}
|
|
9716
|
+
<article class="record-card character-card preview-record-card has-card-edit has-pin-control" data-open-character="${esc(item.id)}" role="button" tabindex="0" aria-label="查看角色 ${esc(item.name)}">${characterPinButton(item)}${characterFavoriteButton(item)}${recordCardEditButton("edit-character", item.id, `角色“${item.name}”`)}
|
|
9501
9717
|
<div class="character-card-heading">${characterAvatarHtml(item)}<h3>${esc(item.name)}</h3>${entityLifecycleBadge(item.isDead, "已死亡")}${characterLockBadge(item)}</div>
|
|
9502
9718
|
${item.attributes?.identity ? `<p class="character-identity">${esc(item.attributes.identity)}</p>` : ""}
|
|
9503
9719
|
<div class="character-gender"><b>性别</b><span class="pill">${esc(characterGenderLabel(item.gender))}</span></div>
|
|
@@ -9574,6 +9790,17 @@ async function renderCharacters(page = characterListPage) {
|
|
|
9574
9790
|
toast(`角色收藏状态更新失败:${error.message}`, "error");
|
|
9575
9791
|
}
|
|
9576
9792
|
}));
|
|
9793
|
+
$("#module-content").querySelectorAll("[data-character-pin]").forEach((button) => button.addEventListener("click", async () => {
|
|
9794
|
+
const item = pageCharacters.find((candidate) => candidate.id === button.dataset.characterPin);
|
|
9795
|
+
if (!item || !canEditModule("characters")) return;
|
|
9796
|
+
button.disabled = true;
|
|
9797
|
+
try {
|
|
9798
|
+
await toggleCharacterPin(item);
|
|
9799
|
+
} catch (error) {
|
|
9800
|
+
button.disabled = false;
|
|
9801
|
+
toast(`角色置顶状态更新失败:${error.message}`, "error");
|
|
9802
|
+
}
|
|
9803
|
+
}));
|
|
9577
9804
|
const readSelectedValues = (selector) => [...$(selector).querySelectorAll('input[type="checkbox"]:checked')].map((input) => input.value);
|
|
9578
9805
|
$("#character-race-filter").addEventListener("change", async () => {
|
|
9579
9806
|
characterFiltersPanelOpen = true;
|
|
@@ -9728,14 +9955,14 @@ async function renderOrganizations(page = moduleListPages.organizations) {
|
|
|
9728
9955
|
moduleListPages.organizations = pageResult.page;
|
|
9729
9956
|
const layout = readModuleLayout();
|
|
9730
9957
|
const canEditOrganizations = canEditModule("organizations");
|
|
9731
|
-
const organizationActions = (item, { cardControl = false } = {}) => `${recordFavoriteButton("organization", item, { cardControl })}${canEditOrganizations
|
|
9958
|
+
const organizationActions = (item, { cardControl = false } = {}) => `${recordPinButton("organization", item, { cardControl })}${recordFavoriteButton("organization", item, { cardControl })}${canEditOrganizations
|
|
9732
9959
|
? recordCardEditButton("edit-organization", item.id, `组织“${item.name}”`)
|
|
9733
9960
|
: recordHistoryButton("organization", item.id, item.name)}`;
|
|
9734
9961
|
const organizationCardActions = (item) => canEditOrganizations
|
|
9735
9962
|
? organizationActions(item, { cardControl: true })
|
|
9736
9963
|
: `<div class="card-actions">${organizationActions(item)}</div>`;
|
|
9737
9964
|
const organizationCards = () => `<div class="card-grid organization-grid">${pageResult.items.map((item) => `
|
|
9738
|
-
<article class="record-card organization-card preview-record-card${canEditOrganizations ? " has-card-edit has-favorite-control" : ""}" data-open-organization="${esc(item.id)}" role="button" tabindex="0" aria-label="查看组织 ${esc(item.name)}"><small>${item.memberIds.length} 位成员 · ${(item.settingsCount ?? item.settings?.length ?? 0) ? "已填写组织设定" : "暂无组织设定"}</small>
|
|
9965
|
+
<article class="record-card organization-card preview-record-card${canEditOrganizations ? " has-card-edit has-favorite-control has-pin-control" : ""}" data-open-organization="${esc(item.id)}" role="button" tabindex="0" aria-label="查看组织 ${esc(item.name)}"><small>${item.memberIds.length} 位成员 · ${(item.settingsCount ?? item.settings?.length ?? 0) ? "已填写组织设定" : "暂无组织设定"}</small>
|
|
9739
9966
|
<h3>${esc(item.name)}${entityLifecycleBadge(item.isDissolved, "已解散")}</h3><p>${esc(item.description || "尚未填写组织简介")}</p>
|
|
9740
9967
|
<div class="organization-settings">${item.settingsCount ? `<span class="pill">${item.settingsCount} 条组织设定,打开查看详情</span>` : '<span class="pill">暂无组织设定</span>'}</div>
|
|
9741
9968
|
<p class="organization-members">成员:${item.members.length ? item.members.map((member) => esc(member.name)).join("、") : "暂无绑定角色"}</p>
|
|
@@ -9764,6 +9991,10 @@ async function renderOrganizations(page = moduleListPages.organizations) {
|
|
|
9764
9991
|
moduleListPages.organizations = 1;
|
|
9765
9992
|
await renderOrganizations(1);
|
|
9766
9993
|
});
|
|
9994
|
+
bindRecordPinButtons("organization", pageResult.items, async () => {
|
|
9995
|
+
moduleListPages.organizations = 1;
|
|
9996
|
+
await renderOrganizations(1);
|
|
9997
|
+
});
|
|
9767
9998
|
bindEntityHistoryButtons(async () => { await renderOrganizations(pageResult.page); await loadAiReferences(); });
|
|
9768
9999
|
}
|
|
9769
10000
|
|
|
@@ -13476,6 +13707,10 @@ function openSettingEditor(item = null, { readOnly = false } = {}) {
|
|
|
13476
13707
|
const editButton = $("#setting-editor-edit");
|
|
13477
13708
|
editButton.classList.toggle("hidden", !readOnly || !canEditModule("settings"));
|
|
13478
13709
|
editButton.onclick = () => openSettingEditor(settingEditorItem);
|
|
13710
|
+
bindEntityDetailPinButton($("#setting-editor-pin"), "setting", () => viewOnly ? settingEditorItem : null, (updated) => {
|
|
13711
|
+
settingEditorItem = updated;
|
|
13712
|
+
state.settings = upsertEntityCollection(state.settings, updated);
|
|
13713
|
+
});
|
|
13479
13714
|
bindEntityDetailFavoriteButton($("#setting-editor-favorite"), "setting", () => viewOnly ? settingEditorItem : null, (updated) => {
|
|
13480
13715
|
settingEditorItem = updated;
|
|
13481
13716
|
state.settings = upsertEntityCollection(state.settings, updated);
|
|
@@ -14712,6 +14947,13 @@ async function openCharacterEditor(item = null, { readOnly = false } = {}) {
|
|
|
14712
14947
|
const editButton = $("#character-editor-edit");
|
|
14713
14948
|
editButton.classList.toggle("hidden", !readOnly || !canEditModule("characters"));
|
|
14714
14949
|
editButton.onclick = () => void openCharacterEditor(characterEditorItem);
|
|
14950
|
+
bindEntityDetailPinButton($("#character-editor-pin"), "character", () => viewOnly ? characterEditorItem : null, (updated) => {
|
|
14951
|
+
characterEditorItem = updated;
|
|
14952
|
+
if (loadedAiReferencesWorkId === state.work?.id) {
|
|
14953
|
+
state.characters = upsertEntityCollection(state.characters, updated);
|
|
14954
|
+
renderAiRoleplayCharacterSelect();
|
|
14955
|
+
}
|
|
14956
|
+
});
|
|
14715
14957
|
bindEntityDetailFavoriteButton($("#character-editor-favorite"), "character", () => viewOnly ? characterEditorItem : null, (updated) => {
|
|
14716
14958
|
characterEditorItem = updated;
|
|
14717
14959
|
if (loadedAiReferencesWorkId === state.work?.id) {
|
|
@@ -14905,6 +15147,10 @@ async function openKnowledgeEditor(kind, item, { readOnly = false } = {}) {
|
|
|
14905
15147
|
editButton.textContent = `编辑${label}`;
|
|
14906
15148
|
editButton.classList.toggle("hidden", !readOnly || !canEditModule(module));
|
|
14907
15149
|
editButton.onclick = () => void openKnowledgeEditor(kind, knowledgeEditorItem);
|
|
15150
|
+
bindEntityDetailPinButton($("#knowledge-editor-pin"), "organization", () => !isRace && viewOnly ? knowledgeEditorItem : null, (updated) => {
|
|
15151
|
+
knowledgeEditorItem = updated;
|
|
15152
|
+
state.organizations = upsertEntityCollection(state.organizations, updated);
|
|
15153
|
+
});
|
|
14908
15154
|
bindEntityDetailFavoriteButton($("#knowledge-editor-favorite"), "organization", () => !isRace && viewOnly ? knowledgeEditorItem : null, (updated) => {
|
|
14909
15155
|
knowledgeEditorItem = updated;
|
|
14910
15156
|
state.organizations = upsertEntityCollection(state.organizations, updated);
|
|
@@ -18663,15 +18909,22 @@ $("#import-file").addEventListener("change", async (event) => {
|
|
|
18663
18909
|
body.append("file", file);
|
|
18664
18910
|
body.append("mode", mode);
|
|
18665
18911
|
body.append("expectedVersionNo", String(state.work.versionNo));
|
|
18912
|
+
const importProgress = createImportProgressToast(file.name);
|
|
18666
18913
|
try {
|
|
18667
|
-
const result = await
|
|
18914
|
+
const result = await uploadWithProgress(
|
|
18915
|
+
`/api/works/${state.work.id}/import`,
|
|
18916
|
+
{ method: "POST", body },
|
|
18917
|
+
(progress) => importProgress.update(progress)
|
|
18918
|
+
);
|
|
18668
18919
|
setSaveState(mode === "append" ? "已追加" : "已覆盖");
|
|
18669
18920
|
state.work = result.tree;
|
|
18670
18921
|
renderTree();
|
|
18671
18922
|
const completion = mode === "append" ? "正文追加完成" : "正文覆盖完成";
|
|
18672
|
-
toast(result.warnings.length ? `${completion}:${result.warnings.join(";")}` : completion);
|
|
18673
18923
|
if (result.firstImportedChapterId) await selectChapter(result.firstImportedChapterId);
|
|
18924
|
+
importProgress.close();
|
|
18925
|
+
toast(result.warnings.length ? `${completion}:${result.warnings.join(";")}` : completion);
|
|
18674
18926
|
} catch (error) {
|
|
18927
|
+
importProgress.close();
|
|
18675
18928
|
toast(error.message, "error");
|
|
18676
18929
|
if (state.dirty) scheduleChapterAutoSave();
|
|
18677
18930
|
}
|
|
@@ -18686,11 +18939,18 @@ $("#new-import-file").addEventListener("change", async (event) => {
|
|
|
18686
18939
|
body.append("title", metadata.title ?? "");
|
|
18687
18940
|
body.append("author", metadata.author ?? "");
|
|
18688
18941
|
body.append("description", metadata.description ?? "");
|
|
18942
|
+
const importProgress = createImportProgressToast(file.name);
|
|
18689
18943
|
try {
|
|
18690
|
-
const result = await
|
|
18691
|
-
|
|
18944
|
+
const result = await uploadWithProgress(
|
|
18945
|
+
"/api/works/import",
|
|
18946
|
+
{ method: "POST", body },
|
|
18947
|
+
(progress) => importProgress.update(progress)
|
|
18948
|
+
);
|
|
18692
18949
|
await loadWorks(result.work.id);
|
|
18950
|
+
importProgress.close();
|
|
18951
|
+
toast(result.warnings.length ? `作品已导入:${result.warnings.join(";")}` : "作品已导入");
|
|
18693
18952
|
} catch (error) {
|
|
18953
|
+
importProgress.close();
|
|
18694
18954
|
toast(error.message, "error");
|
|
18695
18955
|
} finally {
|
|
18696
18956
|
state.pendingImportMeta = null;
|
package/dist/public/index.html
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<link rel="icon" href="/icon.svg?v=20260712" type="image/svg+xml">
|
|
11
11
|
<link rel="manifest" href="/site.webmanifest">
|
|
12
12
|
<link rel="stylesheet" href="/vendor/vditor/dist/index.css?v=3.11.2">
|
|
13
|
-
<link rel="stylesheet" href="/styles.css?v=20260816-task-scope-volume-collapse-v2&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v3&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=galaxy-compact-controls-v2&feature=galaxy-motion-mode-v2&feature=chapter-search-replace-v3&feature=task-auto-run-ring-center-v3&feature=character-relationship-delete-v1&feature=ai-assistant-workspace-v2&feature=mobile-module-tab-position-v1&feature=volume-detail-icon-v1&feature=editor-actions-flow-v1&feature=reader-controls-subpanel-v1&feature=reader-focus-ring-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v2&feature=ai-composer-square-controls-v2&feature=annotation-line-counts-v1&feature=line-number-gutter-fill-v1&feature=ai-relationship-roleplay-v1&feature=ai-model-picker-v1&feature=markdown-word-count-five-digit-v2&feature=ai-stream-character-count-stable-v1&feature=annotation-marker-offset-v1&feature=mobile-ai-entry-hidden-v1&feature=phone-client-entry-v1&feature=ai-stream-idle-timeout-v1&feature=ai-user-message-width-v2&feature=ai-chat-image-attachments-v9&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=toast-click-dismiss-v3&feature=character-avatar-v6&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-token-quota-positive-v5&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-stream-connection-seconds-v1&feature=ai-token-usage-estimated-price-v1&feature=record-favorites-v1&feature=entity-detail-favorites-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v1&feature=admin-account-identity-v2&feature=task-detail-failure-orange-v1">
|
|
13
|
+
<link rel="stylesheet" href="/styles.css?v=20260816-task-scope-volume-collapse-v2&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v3&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=galaxy-compact-controls-v2&feature=galaxy-motion-mode-v2&feature=chapter-search-replace-v3&feature=task-auto-run-ring-center-v3&feature=character-relationship-delete-v1&feature=ai-assistant-workspace-v2&feature=mobile-module-tab-position-v1&feature=volume-detail-icon-v1&feature=editor-actions-flow-v1&feature=reader-controls-subpanel-v1&feature=reader-focus-ring-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v2&feature=ai-composer-square-controls-v2&feature=annotation-line-counts-v1&feature=line-number-gutter-fill-v1&feature=ai-relationship-roleplay-v1&feature=ai-model-picker-v1&feature=markdown-word-count-five-digit-v2&feature=ai-stream-character-count-stable-v1&feature=annotation-marker-offset-v1&feature=mobile-ai-entry-hidden-v1&feature=phone-client-entry-v1&feature=ai-stream-idle-timeout-v1&feature=ai-user-message-width-v2&feature=ai-chat-image-attachments-v9&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=toast-click-dismiss-v3&feature=character-avatar-v6&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-token-quota-positive-v5&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-stream-connection-seconds-v1&feature=ai-token-usage-estimated-price-v1&feature=record-favorites-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v1&feature=admin-account-identity-v2&feature=task-detail-failure-orange-v1&feature=character-card-header-alignment-v6&feature=character-card-title-fit-v2&feature=book-import-progress-v1">
|
|
14
14
|
</head>
|
|
15
15
|
<body class="auth-pending">
|
|
16
16
|
<section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
|
|
@@ -476,6 +476,7 @@
|
|
|
476
476
|
</section>
|
|
477
477
|
|
|
478
478
|
<div id="toast-region" class="toast-region" data-position="bottom-right" aria-live="polite" popover="manual"></div>
|
|
479
|
+
<div id="import-progress-region" class="toast-region import-progress-region" data-position="bottom-right" aria-live="polite" popover="manual"></div>
|
|
479
480
|
<div id="chapter-type-menu" class="chapter-type-menu hidden" role="menu" aria-label="章节操作" data-testid="chapter-type-menu">
|
|
480
481
|
<strong>标记章节</strong>
|
|
481
482
|
<div>
|
|
@@ -658,6 +659,7 @@
|
|
|
658
659
|
</div>
|
|
659
660
|
</div>
|
|
660
661
|
<div class="entity-editor-mode-actions">
|
|
662
|
+
<button id="setting-editor-pin" class="entity-detail-pin-button hidden" type="button" aria-pressed="false"></button>
|
|
661
663
|
<button id="setting-editor-favorite" class="entity-detail-favorite-button hidden" type="button" aria-pressed="false"></button>
|
|
662
664
|
<button id="setting-editor-edit" class="ghost-button hidden" type="button">编辑设定</button>
|
|
663
665
|
<button id="setting-editor-confirm" class="ghost-button hidden" type="button">确认候选</button>
|
|
@@ -685,6 +687,7 @@
|
|
|
685
687
|
<div class="character-editor-title-row"><h1 id="character-editor-title">新建角色</h1><span id="character-editor-version" class="character-version-badge">新档案</span><span id="character-editor-readonly-badge" class="entity-editor-readonly-badge hidden">只读模式</span></div>
|
|
686
688
|
</div>
|
|
687
689
|
<div class="character-editor-header-actions">
|
|
690
|
+
<button id="character-editor-pin" class="entity-detail-pin-button hidden" type="button" aria-pressed="false"></button>
|
|
688
691
|
<button id="character-editor-favorite" class="entity-detail-favorite-button hidden" type="button" aria-pressed="false"></button>
|
|
689
692
|
<button id="character-editor-edit" class="primary-button hidden" type="button">编辑角色</button>
|
|
690
693
|
<button id="character-history-button" class="ghost-button" type="button" aria-controls="character-history-panel" aria-expanded="false">版本历史</button>
|
|
@@ -721,6 +724,7 @@
|
|
|
721
724
|
</div>
|
|
722
725
|
<div class="character-editor-header-actions">
|
|
723
726
|
<span id="knowledge-editor-header-note" class="knowledge-editor-header-note">独立资料页</span>
|
|
727
|
+
<button id="knowledge-editor-pin" class="entity-detail-pin-button hidden" type="button" aria-pressed="false"></button>
|
|
724
728
|
<button id="knowledge-editor-favorite" class="entity-detail-favorite-button hidden" type="button" aria-pressed="false"></button>
|
|
725
729
|
<button id="knowledge-editor-edit" class="primary-button hidden" type="button">编辑档案</button>
|
|
726
730
|
<button id="knowledge-editor-history" class="ghost-button hidden" type="button">版本历史</button>
|
|
@@ -1258,6 +1262,6 @@
|
|
|
1258
1262
|
<div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
|
|
1259
1263
|
<script id="vditorIconScript" src="/vendor/vditor/dist/js/icons/ant.js?v=3.11.2"></script>
|
|
1260
1264
|
<script src="/vendor/vditor/dist/index.min.js?v=3.11.2"></script>
|
|
1261
|
-
<script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v2&feature=analysis-task-queue-refresh-v1&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=ai-session-id-copy-v2&feature=galaxy-motion-mode-v3&feature=galaxy-edge-label-threshold-v1&feature=calculate-time-tool-v2&feature=analysis-task-expired-toast-v1&feature=global-replace-volume-v1&feature=chapter-search-replace-v1&feature=chapter-save-toast-v1&feature=character-relationship-delete-v1&feature=character-relationship-group-v1&feature=analysis-task-stability-delay-v1&feature=ai-assistant-workspace-v2&feature=volume-detail-icon-v1&feature=volume-story-order-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v3&feature=annotation-permissions-v1&feature=annotation-line-counts-v1&feature=ai-relationship-roleplay-v1&feature=ai-roleplay-user-character-visibility-v1&feature=ai-roleplay-story-recall-v1&feature=ai-model-picker-v1&feature=ai-fork-model-unlock-v1&feature=context-percent-format-v1&feature=annotation-precise-locate-v1&feature=markdown-word-count-stable-v1&feature=ai-stream-character-count-stable-v2&feature=ai-process-empty-intermediate-v1&feature=ai-feed-scroll-follow-v1&feature=ai-message-retry-v1&feature=ai-stream-idle-timeout-v2&feature=ai-config-delete-v1&feature=ai-provider-protocol-options-v1&feature=ai-provider-thinking-type-v1&feature=ai-chat-image-attachments-v8&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=ai-image-conversation-model-lock-v1&feature=toast-click-dismiss-v2&feature=system-restart-dialog-delay-v1&feature=character-avatar-v6&feature=character-death-position-v1&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-usage-pricing-label-v1&feature=ai-usage-token-breakdown-v1&feature=ai-usage-pricing-cache-v2&feature=ai-usage-pricing-manual-refresh-v1&feature=ai-monthly-token-quota-v1&feature=ai-provider-token-quota-v1&feature=ai-token-quota-positive-v6&feature=ai-model-thinking-label-v1&feature=ai-model-picker-focus-v1&feature=ai-provider-model-import-v1&feature=ai-assistant-brain-icon-v1&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-token-usage-raw-input-v1&feature=ai-stream-connection-seconds-v1&feature=phone-client-entry-v1&feature=ai-usage-pricing-cache-v1&feature=ai-model-thinking-label-v3&feature=ai-roleplay-speaker-label-v1&feature=toast-modal-host-v1&feature=api-key-copy-existing-v1&feature=character-favorite-v1&feature=record-favorites-v1&feature=ai-token-usage-estimated-price-v1&feature=entity-detail-favorites-v1&feature=roleplay-favorite-label-v1&feature=ai-roleplay-knowledge-tools-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v2&feature=admin-account-identity-v2&feature=ai-provider-analysis-timeout-v1&feature=task-detail-failure-orange-v1"></script>
|
|
1265
|
+
<script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v2&feature=analysis-task-queue-refresh-v1&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=ai-session-id-copy-v2&feature=galaxy-motion-mode-v3&feature=galaxy-edge-label-threshold-v1&feature=calculate-time-tool-v2&feature=analysis-task-expired-toast-v1&feature=global-replace-volume-v1&feature=chapter-search-replace-v1&feature=chapter-save-toast-v1&feature=character-relationship-delete-v1&feature=character-relationship-group-v1&feature=analysis-task-stability-delay-v1&feature=ai-assistant-workspace-v2&feature=volume-detail-icon-v1&feature=volume-story-order-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-v1&feature=ai-roleplay-message-reference-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v3&feature=annotation-permissions-v1&feature=annotation-line-counts-v1&feature=ai-relationship-roleplay-v1&feature=ai-roleplay-user-character-visibility-v1&feature=ai-roleplay-story-recall-v1&feature=ai-model-picker-v1&feature=ai-fork-model-unlock-v1&feature=context-percent-format-v1&feature=annotation-precise-locate-v1&feature=markdown-word-count-stable-v1&feature=ai-stream-character-count-stable-v2&feature=ai-process-empty-intermediate-v1&feature=ai-feed-scroll-follow-v1&feature=ai-message-retry-v1&feature=ai-stream-idle-timeout-v2&feature=ai-config-delete-v1&feature=ai-provider-protocol-options-v1&feature=ai-provider-thinking-type-v1&feature=ai-chat-image-attachments-v8&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=ai-image-conversation-model-lock-v1&feature=toast-click-dismiss-v2&feature=system-restart-dialog-delay-v1&feature=character-avatar-v6&feature=character-death-position-v1&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-usage-pricing-label-v1&feature=ai-usage-token-breakdown-v1&feature=ai-usage-pricing-cache-v2&feature=ai-usage-pricing-manual-refresh-v1&feature=ai-monthly-token-quota-v1&feature=ai-provider-token-quota-v1&feature=ai-token-quota-positive-v6&feature=ai-model-thinking-label-v1&feature=ai-model-picker-focus-v1&feature=ai-provider-model-import-v1&feature=ai-assistant-brain-icon-v1&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-token-usage-raw-input-v1&feature=ai-stream-connection-seconds-v1&feature=phone-client-entry-v1&feature=ai-usage-pricing-cache-v1&feature=ai-model-thinking-label-v3&feature=ai-roleplay-speaker-label-v1&feature=toast-modal-host-v1&feature=api-key-copy-existing-v1&feature=character-favorite-v1&feature=record-favorites-v1&feature=ai-token-usage-estimated-price-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=entity-pin-icon-v1&feature=roleplay-favorite-label-v1&feature=ai-roleplay-knowledge-tools-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v2&feature=admin-account-identity-v2&feature=ai-provider-analysis-timeout-v1&feature=task-detail-failure-orange-v1&feature=book-import-progress-v1&feature=presence-multiple-users-v1"></script>
|
|
1262
1266
|
</body>
|
|
1263
1267
|
</html>
|