@musnows/scriverse 0.8.8 → 0.9.1
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/ai-analysis-timeout.js +12 -0
- package/dist/ai-analysis-timeout.js.map +1 -0
- package/dist/ai.js +411 -91
- package/dist/ai.js.map +1 -1
- package/dist/app.js +291 -22
- package/dist/app.js.map +1 -1
- package/dist/database.js +203 -2
- package/dist/database.js.map +1 -1
- package/dist/desktop-protocol.js +21 -0
- package/dist/desktop-protocol.js.map +1 -0
- package/dist/offline-sync.js +436 -0
- package/dist/offline-sync.js.map +1 -0
- package/dist/public/app.js +228 -20
- package/dist/public/index.html +6 -3
- package/dist/public/styles.css +48 -6
- package/dist/security.js +3 -2
- package/dist/security.js.map +1 -1
- package/dist/server-runtime.js +11 -2
- package/dist/server-runtime.js.map +1 -1
- package/dist/storage-manifest.js +116 -0
- package/dist/storage-manifest.js.map +1 -0
- package/dist/store.js +277 -62
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +158 -29
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/public/app.js
CHANGED
|
@@ -121,6 +121,10 @@ import {
|
|
|
121
121
|
resizeCropRect
|
|
122
122
|
} from "/avatar-crop.js?v=20260725-avatar-crop";
|
|
123
123
|
|
|
124
|
+
const DEFAULT_AI_ANALYSIS_TIMEOUT_SECONDS = 300;
|
|
125
|
+
const MIN_AI_ANALYSIS_TIMEOUT_SECONDS = 30;
|
|
126
|
+
const MAX_AI_ANALYSIS_TIMEOUT_SECONDS = 3_600;
|
|
127
|
+
|
|
124
128
|
const defaultPageSizes = Object.freeze({
|
|
125
129
|
drafts: 30,
|
|
126
130
|
settings: 30,
|
|
@@ -3441,15 +3445,18 @@ async function createNewAiConversation(taskType = "chat") {
|
|
|
3441
3445
|
function favoriteCharactersFirst(characters) {
|
|
3442
3446
|
const items = Array.isArray(characters) ? characters : [];
|
|
3443
3447
|
return [
|
|
3444
|
-
...items.filter((character) => character.isFavorite === true),
|
|
3445
|
-
...items.filter((character) => character.isFavorite !== true)
|
|
3448
|
+
...items.filter((character) => character.isPinned === true && character.isFavorite === true),
|
|
3449
|
+
...items.filter((character) => character.isPinned === true && character.isFavorite !== true),
|
|
3450
|
+
...items.filter((character) => character.isPinned !== true && character.isFavorite === true),
|
|
3451
|
+
...items.filter((character) => character.isPinned !== true && character.isFavorite !== true)
|
|
3446
3452
|
];
|
|
3447
3453
|
}
|
|
3448
3454
|
|
|
3449
3455
|
function roleplayCharacterOptionLabel(character) {
|
|
3450
|
-
const
|
|
3456
|
+
const pinLabel = character?.isPinned === true ? "[置顶]" : "";
|
|
3457
|
+
const favoriteLabel = character?.isFavorite === true ? "[收藏]" : "";
|
|
3451
3458
|
const deathLabel = character?.isDead ? "(已死亡)" : "";
|
|
3452
|
-
return `${favoriteLabel}${String(character?.name ?? "")}${deathLabel}`;
|
|
3459
|
+
return `${pinLabel}${favoriteLabel}${pinLabel || favoriteLabel ? " " : ""}${String(character?.name ?? "")}${deathLabel}`;
|
|
3453
3460
|
}
|
|
3454
3461
|
|
|
3455
3462
|
function renderAiRoleplayCharacterSelect() {
|
|
@@ -4105,6 +4112,31 @@ function aiPromptTextBeforeCursor() {
|
|
|
4105
4112
|
return promptTextFromNode(fragment).replace(/\n$/u, "");
|
|
4106
4113
|
}
|
|
4107
4114
|
|
|
4115
|
+
function aiPromptTextFromRange(range, prompt) {
|
|
4116
|
+
const beforeCursor = range.cloneRange();
|
|
4117
|
+
beforeCursor.selectNodeContents(prompt);
|
|
4118
|
+
beforeCursor.setEnd(range.endContainer, range.endOffset);
|
|
4119
|
+
const fragment = document.createElement("div");
|
|
4120
|
+
fragment.append(beforeCursor.cloneContents());
|
|
4121
|
+
return promptTextFromNode(fragment).replace(/\n$/u, "");
|
|
4122
|
+
}
|
|
4123
|
+
|
|
4124
|
+
function aiPromptTextBoundary(prompt, offset) {
|
|
4125
|
+
const walker = document.createTreeWalker(prompt, NodeFilter.SHOW_TEXT);
|
|
4126
|
+
let remaining = Math.max(0, Number(offset) || 0);
|
|
4127
|
+
let lastTextNode = null;
|
|
4128
|
+
while (walker.nextNode()) {
|
|
4129
|
+
const textNode = walker.currentNode;
|
|
4130
|
+
if (textNode.parentElement?.closest("[data-ai-reference-key]")) continue;
|
|
4131
|
+
lastTextNode = textNode;
|
|
4132
|
+
const length = textNode.textContent?.length ?? 0;
|
|
4133
|
+
if (remaining <= length) return { node: textNode, offset: remaining };
|
|
4134
|
+
remaining -= length;
|
|
4135
|
+
}
|
|
4136
|
+
if (lastTextNode) return { node: lastTextNode, offset: lastTextNode.textContent?.length ?? 0 };
|
|
4137
|
+
return { node: prompt, offset: prompt.childNodes.length };
|
|
4138
|
+
}
|
|
4139
|
+
|
|
4108
4140
|
function hideAiMentionMenu() {
|
|
4109
4141
|
aiMentionMatch = null;
|
|
4110
4142
|
aiMentionRange = null;
|
|
@@ -4193,12 +4225,14 @@ function selectAiMention(button) {
|
|
|
4193
4225
|
id: button.dataset.aiReferenceId,
|
|
4194
4226
|
name: button.dataset.aiReferenceName
|
|
4195
4227
|
};
|
|
4196
|
-
const
|
|
4197
|
-
const
|
|
4198
|
-
const localText = textNode.nodeType === Node.TEXT_NODE ? textNode.textContent?.slice(0, range.startOffset) ?? "" : "";
|
|
4199
|
-
const localMention = findAiMention(localText);
|
|
4228
|
+
const cursorText = aiPromptTextFromRange(aiMentionRange, prompt);
|
|
4229
|
+
const localMention = findAiMention(cursorText);
|
|
4200
4230
|
if (!localMention) return hideAiMentionMenu();
|
|
4201
|
-
range.
|
|
4231
|
+
const range = document.createRange();
|
|
4232
|
+
const startBoundary = aiPromptTextBoundary(prompt, localMention.start);
|
|
4233
|
+
const endBoundary = aiPromptTextBoundary(prompt, cursorText.length);
|
|
4234
|
+
range.setStart(startBoundary.node, startBoundary.offset);
|
|
4235
|
+
range.setEnd(endBoundary.node, endBoundary.offset);
|
|
4202
4236
|
range.deleteContents();
|
|
4203
4237
|
const spacer = document.createTextNode(" ");
|
|
4204
4238
|
range.insertNode(spacer);
|
|
@@ -5143,11 +5177,17 @@ function showAuth(setupRequired, registrationOpen = false, setupTokenRequired =
|
|
|
5143
5177
|
function applyAuthenticatedUser(session) {
|
|
5144
5178
|
state.user = session.user;
|
|
5145
5179
|
state.csrfToken = session.csrfToken;
|
|
5180
|
+
const isSystemAdmin = session.user.isSystemAdmin === true;
|
|
5181
|
+
const accountButton = $("#account-button");
|
|
5146
5182
|
$("#account-name").textContent = session.user.displayName;
|
|
5147
5183
|
renderUserAvatar($("#account-avatar"), session.user);
|
|
5184
|
+
accountButton.setAttribute("aria-label", isSystemAdmin
|
|
5185
|
+
? `账户:${session.user.displayName},系统管理员`
|
|
5186
|
+
: `账户:${session.user.displayName}`);
|
|
5187
|
+
$("#account-admin-mark").classList.toggle("hidden", !isSystemAdmin);
|
|
5148
5188
|
$("#account-menu-display-name").textContent = session.user.displayName;
|
|
5149
5189
|
$("#account-menu-username").textContent = `@${session.user.username}`;
|
|
5150
|
-
$("#account-menu-role").textContent =
|
|
5190
|
+
$("#account-menu-role").textContent = isSystemAdmin ? "系统管理员" : "普通用户";
|
|
5151
5191
|
$("#auth-view").classList.add("hidden");
|
|
5152
5192
|
document.documentElement.classList.remove("login-route");
|
|
5153
5193
|
if (!session.csrfToken) document.body.classList.remove("auth-pending");
|
|
@@ -8677,6 +8717,18 @@ function characterFavoriteIconMarkup() {
|
|
|
8677
8717
|
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>';
|
|
8678
8718
|
}
|
|
8679
8719
|
|
|
8720
|
+
function pushPinIconMarkup() {
|
|
8721
|
+
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>';
|
|
8722
|
+
}
|
|
8723
|
+
|
|
8724
|
+
function characterPinButton(item) {
|
|
8725
|
+
const isPinned = item.isPinned === true;
|
|
8726
|
+
const canPin = canEditModule("characters");
|
|
8727
|
+
const action = isPinned ? "取消置顶" : "置顶";
|
|
8728
|
+
const title = canPin ? action : "当前账户没有角色模块写入权限";
|
|
8729
|
+
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>`;
|
|
8730
|
+
}
|
|
8731
|
+
|
|
8680
8732
|
function characterFavoriteButton(item) {
|
|
8681
8733
|
const isFavorite = item.isFavorite === true;
|
|
8682
8734
|
const canFavorite = canEditModule("characters");
|
|
@@ -8703,6 +8755,17 @@ function recordFavoriteButton(type, item, { cardControl = false } = {}) {
|
|
|
8703
8755
|
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>`;
|
|
8704
8756
|
}
|
|
8705
8757
|
|
|
8758
|
+
function recordPinButton(type, item, { cardControl = false } = {}) {
|
|
8759
|
+
const config = recordFavoriteConfigs[type];
|
|
8760
|
+
if (!config) return "";
|
|
8761
|
+
const isPinned = item.isPinned === true;
|
|
8762
|
+
const canPin = canEditModule(config.module);
|
|
8763
|
+
const action = isPinned ? "取消置顶" : "置顶";
|
|
8764
|
+
const title = canPin ? action : `当前账户没有${config.label}模块写入权限`;
|
|
8765
|
+
const name = String(item[config.nameField] ?? "");
|
|
8766
|
+
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>`;
|
|
8767
|
+
}
|
|
8768
|
+
|
|
8706
8769
|
async function toggleRecordFavorite(type, item, render) {
|
|
8707
8770
|
const config = recordFavoriteConfigs[type];
|
|
8708
8771
|
if (!config) return;
|
|
@@ -8719,6 +8782,22 @@ async function toggleRecordFavorite(type, item, render) {
|
|
|
8719
8782
|
toast(updated.isFavorite ? `已收藏${config.label}“${name}”` : `已取消收藏${config.label}“${name}”`);
|
|
8720
8783
|
}
|
|
8721
8784
|
|
|
8785
|
+
async function toggleRecordPin(type, item, render) {
|
|
8786
|
+
const config = recordFavoriteConfigs[type];
|
|
8787
|
+
if (!config) return;
|
|
8788
|
+
const updated = await api(`/api/${config.resource}/${encodeURIComponent(item.id)}/pin`, {
|
|
8789
|
+
method: "PATCH",
|
|
8790
|
+
body: { isPinned: item.isPinned !== true }
|
|
8791
|
+
});
|
|
8792
|
+
await render();
|
|
8793
|
+
const pinButton = [...$("#module-content").querySelectorAll("[data-record-pin]")].find((button) => (
|
|
8794
|
+
button.dataset.recordPinType === type && button.dataset.recordPin === String(updated.id)
|
|
8795
|
+
));
|
|
8796
|
+
pinButton?.focus({ preventScroll: true });
|
|
8797
|
+
const name = String(updated[config.nameField] ?? "");
|
|
8798
|
+
toast(updated.isPinned ? `已置顶${config.label}“${name}”` : `已取消置顶${config.label}“${name}”`);
|
|
8799
|
+
}
|
|
8800
|
+
|
|
8722
8801
|
function bindRecordFavoriteButtons(type, items, render) {
|
|
8723
8802
|
const config = recordFavoriteConfigs[type];
|
|
8724
8803
|
if (!config) return;
|
|
@@ -8735,6 +8814,22 @@ function bindRecordFavoriteButtons(type, items, render) {
|
|
|
8735
8814
|
}));
|
|
8736
8815
|
}
|
|
8737
8816
|
|
|
8817
|
+
function bindRecordPinButtons(type, items, render) {
|
|
8818
|
+
const config = recordFavoriteConfigs[type];
|
|
8819
|
+
if (!config) return;
|
|
8820
|
+
$("#module-content").querySelectorAll(`[data-record-pin-type="${type}"]`).forEach((button) => button.addEventListener("click", async () => {
|
|
8821
|
+
const item = items.find((candidate) => candidate.id === button.dataset.recordPin);
|
|
8822
|
+
if (!item || !canEditModule(config.module)) return;
|
|
8823
|
+
button.disabled = true;
|
|
8824
|
+
try {
|
|
8825
|
+
await toggleRecordPin(type, item, render);
|
|
8826
|
+
} catch (error) {
|
|
8827
|
+
button.disabled = false;
|
|
8828
|
+
toast(`${config.label}置顶状态更新失败:${error.message}`, "error");
|
|
8829
|
+
}
|
|
8830
|
+
}));
|
|
8831
|
+
}
|
|
8832
|
+
|
|
8738
8833
|
function syncEntityDetailFavoriteButton(button, type, item) {
|
|
8739
8834
|
const config = recordFavoriteConfigs[type];
|
|
8740
8835
|
const visible = Boolean(config && item);
|
|
@@ -8759,6 +8854,30 @@ function syncEntityDetailFavoriteButton(button, type, item) {
|
|
|
8759
8854
|
button.disabled = !canFavorite;
|
|
8760
8855
|
}
|
|
8761
8856
|
|
|
8857
|
+
function syncEntityDetailPinButton(button, type, item) {
|
|
8858
|
+
const config = recordFavoriteConfigs[type];
|
|
8859
|
+
const visible = Boolean(config && item);
|
|
8860
|
+
button.classList.toggle("hidden", !visible);
|
|
8861
|
+
button.innerHTML = pushPinIconMarkup();
|
|
8862
|
+
if (!visible) {
|
|
8863
|
+
button.disabled = true;
|
|
8864
|
+
button.classList.remove("is-pinned");
|
|
8865
|
+
button.setAttribute("aria-pressed", "false");
|
|
8866
|
+
button.removeAttribute("aria-label");
|
|
8867
|
+
button.removeAttribute("title");
|
|
8868
|
+
return;
|
|
8869
|
+
}
|
|
8870
|
+
const isPinned = item.isPinned === true;
|
|
8871
|
+
const canPin = canEditModule(config.module);
|
|
8872
|
+
const action = isPinned ? "取消置顶" : "置顶";
|
|
8873
|
+
const name = String(item[config.nameField] ?? "");
|
|
8874
|
+
button.classList.toggle("is-pinned", isPinned);
|
|
8875
|
+
button.setAttribute("aria-pressed", String(isPinned));
|
|
8876
|
+
button.setAttribute("aria-label", `${action}${config.label}“${name}”`);
|
|
8877
|
+
button.title = canPin ? action : `当前账户没有${config.label}模块写入权限`;
|
|
8878
|
+
button.disabled = !canPin;
|
|
8879
|
+
}
|
|
8880
|
+
|
|
8762
8881
|
function bindEntityDetailFavoriteButton(button, type, getItem, onUpdated) {
|
|
8763
8882
|
syncEntityDetailFavoriteButton(button, type, getItem());
|
|
8764
8883
|
button.onclick = async () => {
|
|
@@ -8783,6 +8902,30 @@ function bindEntityDetailFavoriteButton(button, type, getItem, onUpdated) {
|
|
|
8783
8902
|
};
|
|
8784
8903
|
}
|
|
8785
8904
|
|
|
8905
|
+
function bindEntityDetailPinButton(button, type, getItem, onUpdated) {
|
|
8906
|
+
syncEntityDetailPinButton(button, type, getItem());
|
|
8907
|
+
button.onclick = async () => {
|
|
8908
|
+
const config = recordFavoriteConfigs[type];
|
|
8909
|
+
const item = getItem();
|
|
8910
|
+
if (!config || !item || !canEditModule(config.module)) return;
|
|
8911
|
+
button.disabled = true;
|
|
8912
|
+
try {
|
|
8913
|
+
const updated = await api(`/api/${config.resource}/${encodeURIComponent(item.id)}/pin`, {
|
|
8914
|
+
method: "PATCH",
|
|
8915
|
+
body: { isPinned: item.isPinned !== true }
|
|
8916
|
+
});
|
|
8917
|
+
onUpdated(updated);
|
|
8918
|
+
syncEntityDetailPinButton(button, type, getItem() ?? updated);
|
|
8919
|
+
button.focus({ preventScroll: true });
|
|
8920
|
+
const name = String(updated[config.nameField] ?? "");
|
|
8921
|
+
toast(updated.isPinned ? `已置顶${config.label}“${name}”` : `已取消置顶${config.label}“${name}”`);
|
|
8922
|
+
} catch (error) {
|
|
8923
|
+
syncEntityDetailPinButton(button, type, getItem());
|
|
8924
|
+
toast(`${config.label}置顶状态更新失败:${error instanceof Error ? error.message : "未知错误"}`, "error");
|
|
8925
|
+
}
|
|
8926
|
+
};
|
|
8927
|
+
}
|
|
8928
|
+
|
|
8786
8929
|
function recordCardEditButton(attribute, id, label) {
|
|
8787
8930
|
return `<button class="record-card-edit" type="button" data-${attribute}="${esc(id)}" aria-label="编辑${esc(label)}" title="编辑">${pencilIconMarkup()}</button>`;
|
|
8788
8931
|
}
|
|
@@ -9121,7 +9264,7 @@ function settingRecordActions(item) {
|
|
|
9121
9264
|
const recordAction = canEditModule("settings")
|
|
9122
9265
|
? recordCardEditButton("edit-setting", item.id, `设定“${item.title}”`)
|
|
9123
9266
|
: recordHistoryButton("setting", item.id, item.title);
|
|
9124
|
-
return `${recordFavoriteButton("setting", item)}${recordAction}`;
|
|
9267
|
+
return `${recordPinButton("setting", item)}${recordFavoriteButton("setting", item)}${recordAction}`;
|
|
9125
9268
|
}
|
|
9126
9269
|
|
|
9127
9270
|
function draftTypeLabel(draftType) {
|
|
@@ -9235,7 +9378,11 @@ function openDraftDialog(item = null, { readOnly = false } = {}) {
|
|
|
9235
9378
|
}
|
|
9236
9379
|
if (item && viewOnly) {
|
|
9237
9380
|
const headerActions = $("#dialog-context-actions");
|
|
9238
|
-
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>' : ""}`;
|
|
9381
|
+
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>' : ""}`;
|
|
9382
|
+
bindEntityDetailPinButton($("#draft-dialog-pin"), "draft", () => draftDialogItem, (updated) => {
|
|
9383
|
+
draftDialogItem = updated;
|
|
9384
|
+
void renderDrafts(moduleListPages.drafts).catch((error) => toast(`想法列表刷新失败:${error instanceof Error ? error.message : "未知错误"}`, "error"));
|
|
9385
|
+
});
|
|
9239
9386
|
bindEntityDetailFavoriteButton($("#draft-dialog-favorite"), "draft", () => draftDialogItem, (updated) => {
|
|
9240
9387
|
draftDialogItem = updated;
|
|
9241
9388
|
void renderDrafts(moduleListPages.drafts).catch((error) => toast(`想法列表刷新失败:${error instanceof Error ? error.message : "未知错误"}`, "error"));
|
|
@@ -9278,7 +9425,7 @@ async function renderDrafts(page = moduleListPages.drafts) {
|
|
|
9278
9425
|
<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>
|
|
9279
9426
|
<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>
|
|
9280
9427
|
</section>`;
|
|
9281
|
-
const actions = (item) => `${recordFavoriteButton("draft", item)}${canEditModule("drafts")
|
|
9428
|
+
const actions = (item) => `${recordPinButton("draft", item)}${recordFavoriteButton("draft", item)}${canEditModule("drafts")
|
|
9282
9429
|
? `${recordCardEditButton("edit-draft", item.id, `想法“${item.title}”`)}${recordHistoryButton("draft", item.id, item.title)}`
|
|
9283
9430
|
: recordHistoryButton("draft", item.id, item.title)}`;
|
|
9284
9431
|
const cards = `<div class="card-grid">${pageResult.items.map((item) => `
|
|
@@ -9340,6 +9487,10 @@ async function renderDrafts(page = moduleListPages.drafts) {
|
|
|
9340
9487
|
moduleListPages.drafts = 1;
|
|
9341
9488
|
await renderDrafts(1);
|
|
9342
9489
|
});
|
|
9490
|
+
bindRecordPinButtons("draft", pageResult.items, async () => {
|
|
9491
|
+
moduleListPages.drafts = 1;
|
|
9492
|
+
await renderDrafts(1);
|
|
9493
|
+
});
|
|
9343
9494
|
bindEntityHistoryButtons(() => renderDrafts(pageResult.page));
|
|
9344
9495
|
}
|
|
9345
9496
|
|
|
@@ -9407,6 +9558,10 @@ async function renderSettings(page = moduleListPages.settings) {
|
|
|
9407
9558
|
moduleListPages.settings = 1;
|
|
9408
9559
|
await renderSettings(1);
|
|
9409
9560
|
});
|
|
9561
|
+
bindRecordPinButtons("setting", pageResult.items, async () => {
|
|
9562
|
+
moduleListPages.settings = 1;
|
|
9563
|
+
await renderSettings(1);
|
|
9564
|
+
});
|
|
9410
9565
|
bindEntityHistoryButtons(async () => { await renderSettings(pageResult.page); await loadAiReferences(); });
|
|
9411
9566
|
};
|
|
9412
9567
|
|
|
@@ -9458,6 +9613,21 @@ async function toggleCharacterFavorite(item) {
|
|
|
9458
9613
|
toast(updated.isFavorite ? `已收藏角色“${updated.name}”` : `已取消收藏角色“${updated.name}”`);
|
|
9459
9614
|
}
|
|
9460
9615
|
|
|
9616
|
+
async function toggleCharacterPin(item) {
|
|
9617
|
+
const updated = await api(`/api/characters/${encodeURIComponent(item.id)}/pin`, {
|
|
9618
|
+
method: "PATCH",
|
|
9619
|
+
body: { isPinned: item.isPinned !== true }
|
|
9620
|
+
});
|
|
9621
|
+
if (loadedAiReferencesWorkId === state.work?.id) {
|
|
9622
|
+
state.characters = upsertEntityCollection(state.characters, updated);
|
|
9623
|
+
renderAiRoleplayCharacterSelect();
|
|
9624
|
+
}
|
|
9625
|
+
characterListPage = 1;
|
|
9626
|
+
await renderCharacters(1);
|
|
9627
|
+
$("#module-content").querySelector(`[data-character-pin="${CSS.escape(String(updated.id))}"]`)?.focus({ preventScroll: true });
|
|
9628
|
+
toast(updated.isPinned ? `已置顶角色“${updated.name}”` : `已取消置顶角色“${updated.name}”`);
|
|
9629
|
+
}
|
|
9630
|
+
|
|
9461
9631
|
async function renderCharacters(page = characterListPage) {
|
|
9462
9632
|
const hasCharacterFilters = characterFilters.raceIds.length > 0
|
|
9463
9633
|
|| characterFilters.organizationIds.length > 0
|
|
@@ -9480,14 +9650,14 @@ async function renderCharacters(page = characterListPage) {
|
|
|
9480
9650
|
[state.races, state.organizations] = [races, organizations];
|
|
9481
9651
|
mountModuleCount(characterPage.total);
|
|
9482
9652
|
const layout = readModuleLayout();
|
|
9483
|
-
const characterActions = (item) => `${characterFavoriteButton(item)}${recordCardEditButton("edit-character", item.id, `角色“${item.name}”`)}`;
|
|
9653
|
+
const characterActions = (item) => `${characterPinButton(item)}${characterFavoriteButton(item)}${recordCardEditButton("edit-character", item.id, `角色“${item.name}”`)}`;
|
|
9484
9654
|
const characterLockBadge = (item) => item.lockedFields.length
|
|
9485
9655
|
? `<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>`
|
|
9486
9656
|
: "";
|
|
9487
9657
|
const characterCards = () => `<div class="card-grid">${pageCharacters.map((item) => {
|
|
9488
9658
|
const details = normalizeCharacterDetails(item.attributes?.details);
|
|
9489
9659
|
return `
|
|
9490
|
-
<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}”`)}
|
|
9660
|
+
<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}”`)}
|
|
9491
9661
|
<div class="character-card-heading">${characterAvatarHtml(item)}<h3>${esc(item.name)}</h3>${entityLifecycleBadge(item.isDead, "已死亡")}${characterLockBadge(item)}</div>
|
|
9492
9662
|
${item.attributes?.identity ? `<p class="character-identity">${esc(item.attributes.identity)}</p>` : ""}
|
|
9493
9663
|
<div class="character-gender"><b>性别</b><span class="pill">${esc(characterGenderLabel(item.gender))}</span></div>
|
|
@@ -9564,6 +9734,17 @@ async function renderCharacters(page = characterListPage) {
|
|
|
9564
9734
|
toast(`角色收藏状态更新失败:${error.message}`, "error");
|
|
9565
9735
|
}
|
|
9566
9736
|
}));
|
|
9737
|
+
$("#module-content").querySelectorAll("[data-character-pin]").forEach((button) => button.addEventListener("click", async () => {
|
|
9738
|
+
const item = pageCharacters.find((candidate) => candidate.id === button.dataset.characterPin);
|
|
9739
|
+
if (!item || !canEditModule("characters")) return;
|
|
9740
|
+
button.disabled = true;
|
|
9741
|
+
try {
|
|
9742
|
+
await toggleCharacterPin(item);
|
|
9743
|
+
} catch (error) {
|
|
9744
|
+
button.disabled = false;
|
|
9745
|
+
toast(`角色置顶状态更新失败:${error.message}`, "error");
|
|
9746
|
+
}
|
|
9747
|
+
}));
|
|
9567
9748
|
const readSelectedValues = (selector) => [...$(selector).querySelectorAll('input[type="checkbox"]:checked')].map((input) => input.value);
|
|
9568
9749
|
$("#character-race-filter").addEventListener("change", async () => {
|
|
9569
9750
|
characterFiltersPanelOpen = true;
|
|
@@ -9718,14 +9899,14 @@ async function renderOrganizations(page = moduleListPages.organizations) {
|
|
|
9718
9899
|
moduleListPages.organizations = pageResult.page;
|
|
9719
9900
|
const layout = readModuleLayout();
|
|
9720
9901
|
const canEditOrganizations = canEditModule("organizations");
|
|
9721
|
-
const organizationActions = (item, { cardControl = false } = {}) => `${recordFavoriteButton("organization", item, { cardControl })}${canEditOrganizations
|
|
9902
|
+
const organizationActions = (item, { cardControl = false } = {}) => `${recordPinButton("organization", item, { cardControl })}${recordFavoriteButton("organization", item, { cardControl })}${canEditOrganizations
|
|
9722
9903
|
? recordCardEditButton("edit-organization", item.id, `组织“${item.name}”`)
|
|
9723
9904
|
: recordHistoryButton("organization", item.id, item.name)}`;
|
|
9724
9905
|
const organizationCardActions = (item) => canEditOrganizations
|
|
9725
9906
|
? organizationActions(item, { cardControl: true })
|
|
9726
9907
|
: `<div class="card-actions">${organizationActions(item)}</div>`;
|
|
9727
9908
|
const organizationCards = () => `<div class="card-grid organization-grid">${pageResult.items.map((item) => `
|
|
9728
|
-
<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>
|
|
9909
|
+
<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>
|
|
9729
9910
|
<h3>${esc(item.name)}${entityLifecycleBadge(item.isDissolved, "已解散")}</h3><p>${esc(item.description || "尚未填写组织简介")}</p>
|
|
9730
9911
|
<div class="organization-settings">${item.settingsCount ? `<span class="pill">${item.settingsCount} 条组织设定,打开查看详情</span>` : '<span class="pill">暂无组织设定</span>'}</div>
|
|
9731
9912
|
<p class="organization-members">成员:${item.members.length ? item.members.map((member) => esc(member.name)).join("、") : "暂无绑定角色"}</p>
|
|
@@ -9754,6 +9935,10 @@ async function renderOrganizations(page = moduleListPages.organizations) {
|
|
|
9754
9935
|
moduleListPages.organizations = 1;
|
|
9755
9936
|
await renderOrganizations(1);
|
|
9756
9937
|
});
|
|
9938
|
+
bindRecordPinButtons("organization", pageResult.items, async () => {
|
|
9939
|
+
moduleListPages.organizations = 1;
|
|
9940
|
+
await renderOrganizations(1);
|
|
9941
|
+
});
|
|
9757
9942
|
bindEntityHistoryButtons(async () => { await renderOrganizations(pageResult.page); await loadAiReferences(); });
|
|
9758
9943
|
}
|
|
9759
9944
|
|
|
@@ -11375,7 +11560,7 @@ function openTaskDetailDialog(task, trace) {
|
|
|
11375
11560
|
<p><strong>状态</strong> ${esc(analysisTaskStatusLabel(task.status))} · 进度 ${Number(task.progress ?? 0)}%</p>
|
|
11376
11561
|
<p><strong>范围摘要</strong> ${esc(task.scopeSummary || "未指定")}</p>
|
|
11377
11562
|
<div><strong>范围详情</strong><ul>${detailHtml}</ul></div>
|
|
11378
|
-
<div><strong>失败信息</strong>${failureHtml}${identityRepairHtml}</div>
|
|
11563
|
+
<div class="task-detail-failures"><strong>失败信息</strong>${failureHtml}${identityRepairHtml}</div>
|
|
11379
11564
|
<div><strong>结果摘要</strong>${resultPreview}</div>
|
|
11380
11565
|
${canRerunAnalysisTask(task) ? `<div class="task-detail-actions"><button class="primary-button" type="button" data-rerun-task-detail="${esc(task.id)}">按原配置重新执行</button><button class="ghost-button" type="button" data-rerun-task-model="${esc(task.id)}">换模型重试</button><small>新任务会重新读取当前正文、设定和人物资料,旧任务记录保持不变。</small></div>` : ""}
|
|
11381
11566
|
</section>
|
|
@@ -11414,7 +11599,7 @@ function renderProviderCards(providers, models, protocolOptions) {
|
|
|
11414
11599
|
: "";
|
|
11415
11600
|
return `
|
|
11416
11601
|
<article class="record-card provider-card ${provider.status === "disabled" ? "is-disabled" : ""}"><div class="provider-card-meta"><small>平台级 · ${esc(providerProtocolLabel(provider.protocol, protocolOptions))} · ${esc(providerConnectionLabel(provider.connectionStatus))}</small><span class="provider-status-badge ${providerStatusClass}">${esc(providerStatusLabel(provider.status))}</span></div><h3>${esc(provider.name)}</h3>
|
|
11417
|
-
${disabledNotice}<p>${esc(provider.baseUrl)}\n密钥:${esc(provider.apiKey)}\n最大输出参数:${esc(provider.maxTokensParameter ?? "max_tokens")}\n思考类型:${esc(provider.thinkingType ?? "enabled")}\n并发:${provider.concurrencyLimit} · 每分钟请求:${provider.rpmLimit}\n每日 Token 额度:${provider.dailyTokenQuota === null || provider.dailyTokenQuota === undefined ? "未限制" : Number(provider.dailyTokenQuota).toLocaleString("zh-CN")} · 每月 Token 额度:${provider.monthlyTokenQuota === null || provider.monthlyTokenQuota === undefined ? "未限制" : Number(provider.monthlyTokenQuota).toLocaleString("zh-CN")}${provider.lastError ? `\n错误:${esc(provider.lastError)}` : ""}</p>
|
|
11602
|
+
${disabledNotice}<p>${esc(provider.baseUrl)}\n密钥:${esc(provider.apiKey)}\n最大输出参数:${esc(provider.maxTokensParameter ?? "max_tokens")}\n思考类型:${esc(provider.thinkingType ?? "enabled")}\n并发:${provider.concurrencyLimit} · 每分钟请求:${provider.rpmLimit}\n分析请求超时:${Number(provider.analysisTimeoutSeconds ?? DEFAULT_AI_ANALYSIS_TIMEOUT_SECONDS).toLocaleString("zh-CN")} 秒\n每日 Token 额度:${provider.dailyTokenQuota === null || provider.dailyTokenQuota === undefined ? "未限制" : Number(provider.dailyTokenQuota).toLocaleString("zh-CN")} · 每月 Token 额度:${provider.monthlyTokenQuota === null || provider.monthlyTokenQuota === undefined ? "未限制" : Number(provider.monthlyTokenQuota).toLocaleString("zh-CN")}${provider.lastError ? `\n错误:${esc(provider.lastError)}` : ""}</p>
|
|
11418
11603
|
<div class="provider-models">${providerModels.map((model) => {
|
|
11419
11604
|
const modelUnavailable = !isSelectableModel({ ...model, providerStatus: provider.status, providerConnectionStatus: provider.connectionStatus });
|
|
11420
11605
|
const modelStatus = !model.enabled
|
|
@@ -13466,6 +13651,10 @@ function openSettingEditor(item = null, { readOnly = false } = {}) {
|
|
|
13466
13651
|
const editButton = $("#setting-editor-edit");
|
|
13467
13652
|
editButton.classList.toggle("hidden", !readOnly || !canEditModule("settings"));
|
|
13468
13653
|
editButton.onclick = () => openSettingEditor(settingEditorItem);
|
|
13654
|
+
bindEntityDetailPinButton($("#setting-editor-pin"), "setting", () => viewOnly ? settingEditorItem : null, (updated) => {
|
|
13655
|
+
settingEditorItem = updated;
|
|
13656
|
+
state.settings = upsertEntityCollection(state.settings, updated);
|
|
13657
|
+
});
|
|
13469
13658
|
bindEntityDetailFavoriteButton($("#setting-editor-favorite"), "setting", () => viewOnly ? settingEditorItem : null, (updated) => {
|
|
13470
13659
|
settingEditorItem = updated;
|
|
13471
13660
|
state.settings = upsertEntityCollection(state.settings, updated);
|
|
@@ -14702,6 +14891,13 @@ async function openCharacterEditor(item = null, { readOnly = false } = {}) {
|
|
|
14702
14891
|
const editButton = $("#character-editor-edit");
|
|
14703
14892
|
editButton.classList.toggle("hidden", !readOnly || !canEditModule("characters"));
|
|
14704
14893
|
editButton.onclick = () => void openCharacterEditor(characterEditorItem);
|
|
14894
|
+
bindEntityDetailPinButton($("#character-editor-pin"), "character", () => viewOnly ? characterEditorItem : null, (updated) => {
|
|
14895
|
+
characterEditorItem = updated;
|
|
14896
|
+
if (loadedAiReferencesWorkId === state.work?.id) {
|
|
14897
|
+
state.characters = upsertEntityCollection(state.characters, updated);
|
|
14898
|
+
renderAiRoleplayCharacterSelect();
|
|
14899
|
+
}
|
|
14900
|
+
});
|
|
14705
14901
|
bindEntityDetailFavoriteButton($("#character-editor-favorite"), "character", () => viewOnly ? characterEditorItem : null, (updated) => {
|
|
14706
14902
|
characterEditorItem = updated;
|
|
14707
14903
|
if (loadedAiReferencesWorkId === state.work?.id) {
|
|
@@ -14895,6 +15091,10 @@ async function openKnowledgeEditor(kind, item, { readOnly = false } = {}) {
|
|
|
14895
15091
|
editButton.textContent = `编辑${label}`;
|
|
14896
15092
|
editButton.classList.toggle("hidden", !readOnly || !canEditModule(module));
|
|
14897
15093
|
editButton.onclick = () => void openKnowledgeEditor(kind, knowledgeEditorItem);
|
|
15094
|
+
bindEntityDetailPinButton($("#knowledge-editor-pin"), "organization", () => !isRace && viewOnly ? knowledgeEditorItem : null, (updated) => {
|
|
15095
|
+
knowledgeEditorItem = updated;
|
|
15096
|
+
state.organizations = upsertEntityCollection(state.organizations, updated);
|
|
15097
|
+
});
|
|
14898
15098
|
bindEntityDetailFavoriteButton($("#knowledge-editor-favorite"), "organization", () => !isRace && viewOnly ? knowledgeEditorItem : null, (updated) => {
|
|
14899
15099
|
knowledgeEditorItem = updated;
|
|
14900
15100
|
state.organizations = upsertEntityCollection(state.organizations, updated);
|
|
@@ -15810,6 +16010,7 @@ function openProviderDialog(item, protocolOptions = platformAiProtocolOptions) {
|
|
|
15810
16010
|
const monthlyTokenQuota = item?.monthlyTokenQuota ?? null;
|
|
15811
16011
|
const providerTokenQuotaFields = `<div class="form-field provider-token-quota-fields" data-provider-token-quota-fields><span>供应商 Token 额度</span><small>按服务器部署时区统计该供应商跨所有小说的输入与输出 Token;与单个小说额度独立。额度必须设置为大于 0;低于每日 10,000 或每月 1,000,000 时仅提示风险。</small><div class="provider-token-quota-row"><label class="checkbox-field provider-token-quota-toggle"><input name="dailyTokenQuotaEnabled" type="checkbox" ${dailyTokenQuota === null ? "" : "checked"}><span>启用每日额度</span></label><label class="provider-token-quota-input">每日额度<input name="dailyTokenQuota" type="number" min="1" max="2000000000" step="1" value="${esc(String(dailyTokenQuota ?? 10000))}" aria-label="供应商每日 Token 额度" ${dailyTokenQuota === null ? "disabled" : ""}></label></div><div class="provider-token-quota-row"><label class="checkbox-field provider-token-quota-toggle"><input name="monthlyTokenQuotaEnabled" type="checkbox" ${monthlyTokenQuota === null ? "" : "checked"}><span>启用每月额度</span></label><label class="provider-token-quota-input">每月额度<input name="monthlyTokenQuota" type="number" min="1" max="2000000000" step="1" value="${esc(String(monthlyTokenQuota ?? 10000))}" aria-label="供应商每月 Token 额度" ${monthlyTokenQuota === null ? "disabled" : ""}></label></div></div>`;
|
|
15812
16012
|
const thinkingTypeField = `<div class="form-field provider-thinking-type-field" data-provider-thinking-type-field><span>思考类型(开启时)</span><label class="checkbox-field"><input name="useAdaptiveThinking" type="checkbox" ${useAdaptiveThinking ? "checked" : ""} aria-describedby="provider-thinking-type-hint"><span>使用 adaptive(关闭时发送 enabled)</span></label><small id="provider-thinking-type-hint">关闭模型思考时仍发送 disabled;请只在供应商支持 adaptive 时开启。</small></div>`;
|
|
16013
|
+
const analysisTimeoutField = `<div class="form-field provider-analysis-timeout-field" data-provider-analysis-timeout-field><span>分析请求超时</span><label>单次请求(秒)<input name="analysisTimeoutSeconds" type="number" min="${MIN_AI_ANALYSIS_TIMEOUT_SECONDS}" max="${MAX_AI_ANALYSIS_TIMEOUT_SECONDS}" step="1" value="${esc(String(item?.analysisTimeoutSeconds ?? DEFAULT_AI_ANALYSIS_TIMEOUT_SECONDS))}" aria-describedby="provider-analysis-timeout-hint"></label><small id="provider-analysis-timeout-hint">用于全书分析和关系分析的单次非流式请求;默认 300 秒,可设置 30–3600 秒。</small></div>`;
|
|
15813
16014
|
openDialog(
|
|
15814
16015
|
item ? "编辑 AI 供应商" : "新建 AI 供应商",
|
|
15815
16016
|
field("name", "显示名称", "text", item?.name)
|
|
@@ -15820,6 +16021,7 @@ function openProviderDialog(item, protocolOptions = platformAiProtocolOptions) {
|
|
|
15820
16021
|
+ thinkingTypeField
|
|
15821
16022
|
+ field("concurrencyLimit", "最大并发请求数", "number", item?.concurrencyLimit ?? 10)
|
|
15822
16023
|
+ field("rpmLimit", "每分钟请求上限", "number", item?.rpmLimit ?? 10)
|
|
16024
|
+
+ analysisTimeoutField
|
|
15823
16025
|
+ providerTokenQuotaFields
|
|
15824
16026
|
+ field("note", "用途备注", "textarea", item?.note)
|
|
15825
16027
|
+ field("enabled", item ? "启用供应商" : "立即启用", "checkbox", item ? item.status === "enabled" : true),
|
|
@@ -15832,11 +16034,17 @@ function openProviderDialog(item, protocolOptions = platformAiProtocolOptions) {
|
|
|
15832
16034
|
thinkingType: form.get("useAdaptiveThinking") === "on" ? "adaptive" : "enabled",
|
|
15833
16035
|
concurrencyLimit: Number(form.get("concurrencyLimit")),
|
|
15834
16036
|
rpmLimit: Number(form.get("rpmLimit")),
|
|
16037
|
+
analysisTimeoutSeconds: Number(form.get("analysisTimeoutSeconds")),
|
|
15835
16038
|
dailyTokenQuota: form.get("dailyTokenQuotaEnabled") === "on" ? Number(form.get("dailyTokenQuota")) : null,
|
|
15836
16039
|
monthlyTokenQuota: form.get("monthlyTokenQuotaEnabled") === "on" ? Number(form.get("monthlyTokenQuota")) : null,
|
|
15837
16040
|
note: form.get("note"),
|
|
15838
16041
|
status: form.get("enabled") === "on" ? "enabled" : "disabled"
|
|
15839
16042
|
};
|
|
16043
|
+
if (
|
|
16044
|
+
!Number.isInteger(body.analysisTimeoutSeconds)
|
|
16045
|
+
|| body.analysisTimeoutSeconds < MIN_AI_ANALYSIS_TIMEOUT_SECONDS
|
|
16046
|
+
|| body.analysisTimeoutSeconds > MAX_AI_ANALYSIS_TIMEOUT_SECONDS
|
|
16047
|
+
) throw new Error("分析请求超时必须设置为 30–3600 秒的整数");
|
|
15840
16048
|
if (!item || String(form.get("apiKey") ?? "").trim()) body.apiKey = form.get("apiKey");
|
|
15841
16049
|
for (const [period, label] of [["daily", "每日"], ["monthly", "每月"]]) {
|
|
15842
16050
|
const value = body[`${period}TokenQuota`];
|
|
@@ -15849,7 +16057,7 @@ function openProviderDialog(item, protocolOptions = platformAiProtocolOptions) {
|
|
|
15849
16057
|
await loadModels();
|
|
15850
16058
|
if (item) toast(connectivityConfigurationSavedToast("provider"));
|
|
15851
16059
|
},
|
|
15852
|
-
item ? "
|
|
16060
|
+
item ? "协议、超时、限流与凭据" : "供应商协议、超时、限流与凭据", {
|
|
15853
16061
|
dangerAction: item ? { label: "删除供应商", onClick: () => deletePlatformProvider(item) } : null
|
|
15854
16062
|
}
|
|
15855
16063
|
);
|
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">
|
|
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-v3&feature=character-card-title-fit-v2">
|
|
14
14
|
</head>
|
|
15
15
|
<body class="auth-pending">
|
|
16
16
|
<section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
<div id="presence-list" class="presence-list" aria-live="polite"></div>
|
|
83
83
|
</section>
|
|
84
84
|
</div>
|
|
85
|
-
<button id="account-button" class="account-button" type="button" aria-haspopup="menu" aria-expanded="false"><span id="account-avatar" class="user-avatar account-avatar" aria-hidden="true"><span class="user-avatar-fallback">作</span></span><span id="account-name">账户</span></button>
|
|
85
|
+
<button id="account-button" class="account-button" type="button" aria-label="账户" aria-haspopup="menu" aria-expanded="false"><span class="account-avatar-frame"><span id="account-avatar" class="user-avatar account-avatar" aria-hidden="true"><span class="user-avatar-fallback">作</span></span><span id="account-admin-mark" class="account-admin-mark hidden" aria-hidden="true"><svg viewBox="0 0 16 16"><path d="M8 1.8 13 3.7v3.7c0 3.1-2 5.6-5 6.8-3-1.2-5-3.7-5-6.8V3.7L8 1.8Z"/><path d="m5.5 7.8 1.6 1.6 3.5-3.5"/></svg></span></span><span class="account-identity"><span id="account-name">账户</span></span></button>
|
|
86
86
|
<div id="account-menu" class="account-menu hidden" role="menu">
|
|
87
87
|
<strong id="account-menu-name" class="account-menu-name"><span id="account-menu-display-name">账户</span><span id="account-menu-username" class="account-menu-username">@account</span></strong><small id="account-menu-role">普通用户</small>
|
|
88
88
|
<button id="onboarding-menu-button" type="button" role="menuitem">功能导览</button>
|
|
@@ -658,6 +658,7 @@
|
|
|
658
658
|
</div>
|
|
659
659
|
</div>
|
|
660
660
|
<div class="entity-editor-mode-actions">
|
|
661
|
+
<button id="setting-editor-pin" class="entity-detail-pin-button hidden" type="button" aria-pressed="false"></button>
|
|
661
662
|
<button id="setting-editor-favorite" class="entity-detail-favorite-button hidden" type="button" aria-pressed="false"></button>
|
|
662
663
|
<button id="setting-editor-edit" class="ghost-button hidden" type="button">编辑设定</button>
|
|
663
664
|
<button id="setting-editor-confirm" class="ghost-button hidden" type="button">确认候选</button>
|
|
@@ -685,6 +686,7 @@
|
|
|
685
686
|
<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
687
|
</div>
|
|
687
688
|
<div class="character-editor-header-actions">
|
|
689
|
+
<button id="character-editor-pin" class="entity-detail-pin-button hidden" type="button" aria-pressed="false"></button>
|
|
688
690
|
<button id="character-editor-favorite" class="entity-detail-favorite-button hidden" type="button" aria-pressed="false"></button>
|
|
689
691
|
<button id="character-editor-edit" class="primary-button hidden" type="button">编辑角色</button>
|
|
690
692
|
<button id="character-history-button" class="ghost-button" type="button" aria-controls="character-history-panel" aria-expanded="false">版本历史</button>
|
|
@@ -721,6 +723,7 @@
|
|
|
721
723
|
</div>
|
|
722
724
|
<div class="character-editor-header-actions">
|
|
723
725
|
<span id="knowledge-editor-header-note" class="knowledge-editor-header-note">独立资料页</span>
|
|
726
|
+
<button id="knowledge-editor-pin" class="entity-detail-pin-button hidden" type="button" aria-pressed="false"></button>
|
|
724
727
|
<button id="knowledge-editor-favorite" class="entity-detail-favorite-button hidden" type="button" aria-pressed="false"></button>
|
|
725
728
|
<button id="knowledge-editor-edit" class="primary-button hidden" type="button">编辑档案</button>
|
|
726
729
|
<button id="knowledge-editor-history" class="ghost-button hidden" type="button">版本历史</button>
|
|
@@ -1258,6 +1261,6 @@
|
|
|
1258
1261
|
<div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
|
|
1259
1262
|
<script id="vditorIconScript" src="/vendor/vditor/dist/js/icons/ant.js?v=3.11.2"></script>
|
|
1260
1263
|
<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"></script>
|
|
1264
|
+
<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"></script>
|
|
1262
1265
|
</body>
|
|
1263
1266
|
</html>
|