@musnows/scriverse 1.1.4 → 1.1.6

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.
@@ -2,8 +2,16 @@ import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
2
2
  import { AppError } from "./errors.js";
3
3
  const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
4
4
  const captchaLength = 4;
5
- const captchaLifetimeMs = 5 * 60_000;
5
+ /** 默认有效期 300 秒。任何配置或调用若短于下限,都会被抬到 180 秒。 */
6
+ export const CAPTCHA_LIFETIME_DEFAULT_MS = 300_000;
7
+ export const CAPTCHA_LIFETIME_MINIMUM_MS = 180_000;
6
8
  const maximumChallenges = 5_000;
9
+ /** 把请求的有效期抬到下限;未提供或非法时使用默认 300 秒。 */
10
+ export function resolveCaptchaLifetimeMs(requestedMs) {
11
+ if (typeof requestedMs !== "number" || !Number.isFinite(requestedMs))
12
+ return CAPTCHA_LIFETIME_DEFAULT_MS;
13
+ return Math.max(CAPTCHA_LIFETIME_MINIMUM_MS, Math.trunc(requestedMs));
14
+ }
7
15
  /**
8
16
  * 5×7 点阵字形会被渲染为 path,而不是 SVG text:这样不会把答案以可直接读取的
9
17
  * 文本节点放进 data URL。它不是对抗专业视觉模型的单独安全边界,但能阻止最基础的
@@ -44,8 +52,15 @@ const glyphPatterns = {
44
52
  "9": ["01110", "10001", "10001", "01111", "00001", "00001", "01110"]
45
53
  };
46
54
  const fallbackGlyph = ["11111", "00001", "00010", "00100", "00100", "00000", "00100"];
55
+ /**
56
+ * 全角字母和数字与半角外观相同,但直接比较会失败。
57
+ * 先做 NFKC,再去掉空白和格式字符(含零宽空格),最后按 en-US 大写。
58
+ */
59
+ export function normalizeCaptchaAnswer(answer) {
60
+ return answer.normalize("NFKC").replace(/[\p{Cf}\p{Z}\s]+/gu, "").toLocaleUpperCase("en-US");
61
+ }
47
62
  function digestAnswer(answer) {
48
- return createHash("sha256").update(answer.toLocaleUpperCase("en-US")).digest();
63
+ return createHash("sha256").update(normalizeCaptchaAnswer(answer)).digest();
49
64
  }
50
65
  function randomCode() {
51
66
  const bytes = randomBytes(captchaLength);
@@ -131,8 +146,13 @@ export function renderCaptchaSvg(code, seed = randomBytes(8)) {
131
146
  export class ImageCaptchaService {
132
147
  options;
133
148
  challenges = new Map();
149
+ lifetimeMs;
134
150
  constructor(options = {}) {
135
151
  this.options = options;
152
+ this.lifetimeMs = resolveCaptchaLifetimeMs(options.lifetimeMs);
153
+ }
154
+ currentTime() {
155
+ return this.options.now?.() ?? Date.now();
136
156
  }
137
157
  create() {
138
158
  this.pruneExpired();
@@ -145,7 +165,7 @@ export class ImageCaptchaService {
145
165
  const svg = renderCaptchaSvg(answer, seed);
146
166
  this.challenges.set(captchaId, {
147
167
  answerDigest: digestAnswer(answer),
148
- expiresAt: Date.now() + captchaLifetimeMs
168
+ expiresAt: this.currentTime() + this.lifetimeMs
149
169
  });
150
170
  return {
151
171
  captchaId,
@@ -153,20 +173,26 @@ export class ImageCaptchaService {
153
173
  ...(this.options.revealAnswer ? { answer } : {})
154
174
  };
155
175
  }
156
- /** 校验并消费验证码;成功或失败都会使该挑战失效。 */
176
+ /**
177
+ * 校验验证码。答案错误时保留挑战,允许用同一张图继续重试。
178
+ * 只有答案正确后才核销。过期,以及已核销或未知的挑战,返回已失效。
179
+ */
157
180
  consume(captchaId, answer) {
181
+ const currentTime = this.currentTime();
158
182
  const challenge = this.challenges.get(captchaId);
159
- this.challenges.delete(captchaId);
160
- if (!challenge || challenge.expiresAt <= Date.now()) {
183
+ if (!challenge || challenge.expiresAt <= currentTime) {
184
+ if (challenge)
185
+ this.challenges.delete(captchaId);
161
186
  throw new AppError(400, "CAPTCHA_INVALID", "验证码已失效,请刷新后重试");
162
187
  }
163
- const provided = digestAnswer(answer.trim());
188
+ const provided = digestAnswer(answer);
164
189
  if (provided.length !== challenge.answerDigest.length || !timingSafeEqual(provided, challenge.answerDigest)) {
165
- throw new AppError(400, "CAPTCHA_INVALID", "验证码不正确");
190
+ throw new AppError(400, "CAPTCHA_INCORRECT", "验证码不正确");
166
191
  }
192
+ this.challenges.delete(captchaId);
167
193
  }
168
194
  pruneExpired() {
169
- const currentTime = Date.now();
195
+ const currentTime = this.currentTime();
170
196
  for (const [captchaId, challenge] of this.challenges) {
171
197
  if (challenge.expiresAt <= currentTime)
172
198
  this.challenges.delete(captchaId);
@@ -1 +1 @@
1
- {"version":3,"file":"image-captcha.js","sourceRoot":"","sources":["../src/image-captcha.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAcvC,MAAM,QAAQ,GAAG,kCAAkC,CAAC;AACpD,MAAM,aAAa,GAAG,CAAC,CAAC;AACxB,MAAM,iBAAiB,GAAG,CAAC,GAAG,MAAM,CAAC;AACrC,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAEhC;;;;GAIG;AACH,MAAM,aAAa,GAAsC;IACvD,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,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"}
1
+ {"version":3,"file":"image-captcha.js","sourceRoot":"","sources":["../src/image-captcha.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAcvC,MAAM,QAAQ,GAAG,kCAAkC,CAAC;AACpD,MAAM,aAAa,GAAG,CAAC,CAAC;AACxB,4CAA4C;AAC5C,MAAM,CAAC,MAAM,2BAA2B,GAAG,OAAO,CAAC;AACnD,MAAM,CAAC,MAAM,2BAA2B,GAAG,OAAO,CAAC;AACnD,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAEhC,qCAAqC;AACrC,MAAM,UAAU,wBAAwB,CAAC,WAAoB;IAC3D,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC;QAAE,OAAO,2BAA2B,CAAC;IACzG,OAAO,IAAI,CAAC,GAAG,CAAC,2BAA2B,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;AACxE,CAAC;AAED;;;;GAIG;AACH,MAAM,aAAa,GAAsC;IACvD,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;IAClE,CAAC,EAAE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"}
@@ -9,14 +9,14 @@ import { createImWorkspace } from "/im.js?v=20260904-im-judge-outcomes-v106";
9
9
  import { findAiMention, listAiMentionOptions, mergeAiReferenceScope } from "/ai-mentions.js?v=20260811-user-message-mentions-v1";
10
10
  import { applyAiSkillCommand, findAiSkillCommand, listAiSlashOptions } from "/ai-skill-menu.js?v=20260921-ai-compact-slash-command-v2";
11
11
  import {
12
+ aiConversationTitleSource,
12
13
  composeRoleplayStoredUserContent,
13
14
  emptyRoleplayScenePin,
14
15
  normalizeRoleplayScenePin,
15
16
  parseRoleplayUserTurn,
16
17
  roleplayScenePinHasContent,
17
- roleplayUserTurnDisplayText,
18
- roleplayUserTurnTitleSource
19
- } from "/roleplay-turn.js?v=20260823-ai-roleplay-scene-turn-v2";
18
+ roleplayUserTurnDisplayText
19
+ } from "/roleplay-turn.js?v=20260930-ai-title-xml-v1";
20
20
  import { shouldShowAiQuickActions } from "/ai-conversation.js?v=20260713-quick-actions";
21
21
  import { createAiChatTabManager, normalizeAiChatTabLimit } from "/ai-chat-tabs.js?v=20260816-ai-chat-switcher-v2";
22
22
  import { aiRequestTargetsState, createAiRequestAbortError, createAiRequestManager, isAiRequestCancellation } from "/ai-request-manager.js?v=20260905-question-stream-v2";
@@ -1977,6 +1977,8 @@ let chapterDraftLineIdState = null;
1977
1977
  let chapterBeforeInputState = null;
1978
1978
  let chapterSearchMatchIndex = -1;
1979
1979
  let chapterSelectionRequestId = 0;
1980
+ let chapterBodyAbort = null;
1981
+ let chapterContentReadyId = null;
1980
1982
  let chapterForeshadowReminderRequestId = 0;
1981
1983
  let chapterForeshadowReminders = [];
1982
1984
  let chapterForeshadowReminderIndex = 0;
@@ -2082,9 +2084,12 @@ function applyChapterEditorMode() {
2082
2084
  $("#chapter-content").readOnly = viewOnly;
2083
2085
  $("#chapter-title").setAttribute("aria-readonly", String(viewOnly));
2084
2086
  $("#chapter-content").setAttribute("aria-readonly", String(viewOnly));
2087
+ const waitingForChapter = chapterEditingLocked();
2085
2088
  editButton.classList.toggle("hidden", permissionBlocked || !state.chapter);
2086
2089
  editButton.classList.toggle("primary-button", chapterEditorReadOnly);
2087
2090
  editButton.classList.toggle("ghost-button", !chapterEditorReadOnly);
2091
+ editButton.disabled = waitingForChapter;
2092
+ editButton.setAttribute("aria-disabled", String(waitingForChapter));
2088
2093
  editButton.textContent = chapterEditorReadOnly ? "编辑" : "预览";
2089
2094
  editButton.setAttribute("aria-pressed", String(!chapterEditorReadOnly));
2090
2095
  editButton.setAttribute("aria-label", chapterEditorReadOnly ? "切换到编辑模式" : "切换到预览模式");
@@ -2095,6 +2100,8 @@ function applyChapterEditorMode() {
2095
2100
  }
2096
2101
 
2097
2102
  function enterChapterEditMode() {
2103
+ // 缓存或占位正文还没被本次请求确认前,编辑按钮不进入编辑。
2104
+ if (chapterEditingLocked()) return;
2098
2105
  if (!state.chapter || !canEditProse()) return;
2099
2106
  chapterEditorReadOnly = false;
2100
2107
  applyChapterEditorMode();
@@ -2105,6 +2112,7 @@ function enterChapterEditMode() {
2105
2112
  }
2106
2113
 
2107
2114
  function toggleChapterEditPreviewMode() {
2115
+ if (chapterEditingLocked()) return;
2108
2116
  if (!state.chapter || !canEditProse()) return;
2109
2117
  if (chapterEditorReadOnly) {
2110
2118
  enterChapterEditMode();
@@ -4962,7 +4970,7 @@ async function updateRoleplayMemoryAction(memory, action) {
4962
4970
  }
4963
4971
 
4964
4972
  function defaultAiConversationTitle(prompt) {
4965
- const normalized = roleplayUserTurnTitleSource(String(prompt ?? "")).replace(/\s+/gu, " ").trim();
4973
+ const normalized = aiConversationTitleSource(String(prompt ?? "")).replace(/\s+/gu, " ").trim();
4966
4974
  return Array.from(normalized).slice(0, 15).join("") || "新对话";
4967
4975
  }
4968
4976
 
@@ -6535,6 +6543,7 @@ async function api(path, options = {}) {
6535
6543
  body: body && typeof body !== "string" ? JSON.stringify(body) : body
6536
6544
  });
6537
6545
  } catch (error) {
6546
+ if (error?.name === "AbortError") throw error;
6538
6547
  if (mobileOfflineRuntime.enabled) {
6539
6548
  const offlineSession = await mobileOfflineRuntime.activateOffline();
6540
6549
  if (offlineSession) return mobileOfflineRuntime.request(path, { method, body });
@@ -7660,6 +7669,7 @@ async function persistChapter({ automatic = false } = {}) {
7660
7669
  const saved = await request;
7661
7670
  if (state.work?.id !== workId || state.chapter?.id !== draft.chapterId) return saved.chapter;
7662
7671
  state.chapter = saved.chapter;
7672
+ chapterContentReadyId = saved.chapter.id;
7663
7673
  state.work = saved.work;
7664
7674
  resetChapterDraftLineIds(state.chapter);
7665
7675
  lastSavedChapterSnapshot = draft;
@@ -9374,6 +9384,9 @@ function renderShelf() {
9374
9384
 
9375
9385
  function resetWorkScopedUiCaches() {
9376
9386
  chapterSelectionRequestId += 1;
9387
+ chapterContentReadyId = null;
9388
+ chapterBodyAbort?.abort();
9389
+ chapterBodyAbort = null;
9377
9390
  clearChapterForeshadowReminders({ invalidateRequest: true });
9378
9391
  invalidateAiConversationNavigation("已切换作品");
9379
9392
  stopBackgroundTaskCenter();
@@ -9861,7 +9874,10 @@ async function submitChapterBatch(event) {
9861
9874
  state.work = await api(`/api/works/${encodeURIComponent(workId)}`);
9862
9875
  const currentEditorVisible = state.module === "editor";
9863
9876
  const currentStillExists = state.chapter && state.work.volumes.some((volume) => volume.chapters.some((chapter) => chapter.id === state.chapter.id));
9864
- if (state.chapter && currentStillExists) state.chapter = await api(`/api/chapters/${encodeURIComponent(state.chapter.id)}`);
9877
+ if (state.chapter && currentStillExists) {
9878
+ state.chapter = await api(`/api/chapters/${encodeURIComponent(state.chapter.id)}`);
9879
+ chapterContentReadyId = state.chapter.id;
9880
+ }
9865
9881
  if (state.chapter && !currentStillExists) {
9866
9882
  state.chapter = null;
9867
9883
  showWelcome(true);
@@ -9997,6 +10013,7 @@ async function deleteChapter(chapterId) {
9997
10013
  const deletingSelectedChapter = state.chapter?.id === chapterId;
9998
10014
  const expectedVersionNo = deletingSelectedChapter ? state.chapter.versionNo : chapter.versionNo;
9999
10015
  await api(`/api/chapters/${chapterId}`, { method: "DELETE", body: { expectedVersionNo } });
10016
+ if (String(chapterContentReadyId ?? "") === String(chapterId)) chapterContentReadyId = null;
10000
10017
  const workId = state.work.id;
10001
10018
  state.work = await api(`/api/works/${workId}`);
10002
10019
  if (deletingSelectedChapter) {
@@ -10215,33 +10232,47 @@ async function resolveCurrentChapterForeshadowReminder() {
10215
10232
  }
10216
10233
  }
10217
10234
 
10218
- async function selectChapter(chapterId, { editMode = false } = {}) {
10219
- imWorkspace.close();
10220
- const workId = state.work?.id;
10221
- if (!workId) return false;
10222
- const selectionGeneration = ++chapterSelectionRequestGeneration;
10223
- const selectionRequestId = ++chapterSelectionRequestId;
10224
- clearChapterForeshadowReminders({ invalidateRequest: true });
10225
- if (state.chapter?.id !== chapterId && !(await confirmDiscardChanges("当前章节有未保存修改,仍要切换吗?"))) {
10226
- if (selectionRequestId === chapterSelectionRequestId) void loadChapterForeshadowReminders();
10227
- return false;
10235
+ function setChapterContentSkeleton(visible) {
10236
+ const skeleton = $("#chapter-content-skeleton");
10237
+ skeleton.classList.toggle("hidden", !visible);
10238
+ skeleton.setAttribute("aria-busy", String(visible));
10239
+ $("#editor-view").setAttribute("aria-busy", String(visible));
10240
+ if (!visible) return;
10241
+ $("#chapter-line-numbers-inner").replaceChildren();
10242
+ $("#chapter-whitespace-inner").replaceChildren();
10243
+ }
10244
+
10245
+ function chapterEditingLocked() {
10246
+ return !state.chapter || String(chapterContentReadyId ?? "") !== String(state.chapter.id);
10247
+ }
10248
+
10249
+ function directoryChapterEntry(chapterId) {
10250
+ for (const volume of state.work?.volumes ?? []) {
10251
+ const chapter = volume.chapters?.find((item) => String(item.id) === String(chapterId));
10252
+ if (chapter) return chapter;
10228
10253
  }
10229
- resetChapterSearchPanel();
10230
- dismissDeleteToasts();
10231
- if (selectionGeneration !== chapterSelectionRequestGeneration || selectionRequestId !== chapterSelectionRequestId || state.work?.id !== workId) return false;
10232
- cancelChapterAutoSave();
10233
- let selectedChapter = state.chapter;
10234
- if (state.chapter?.id !== chapterId) {
10235
- selectedChapter = await api(`/api/chapters/${encodeURIComponent(chapterId)}`);
10236
- if (selectionGeneration !== chapterSelectionRequestGeneration || selectionRequestId !== chapterSelectionRequestId || state.work?.id !== workId) return false;
10237
- if (String(selectedChapter?.id ?? "") !== String(chapterId) || String(selectedChapter?.workId ?? "") !== String(workId)) return false;
10238
- }
10239
- if (!selectedChapter || selectionGeneration !== chapterSelectionRequestGeneration || selectionRequestId !== chapterSelectionRequestId || state.work?.id !== workId) return false;
10240
- state.chapter = selectedChapter;
10241
- mergeChapterDirectoryEntry(state.chapter);
10242
- state.collapsedVolumeIds.delete(state.chapter.volumeId);
10243
- lastSavedChapterSnapshot = { chapterId: state.chapter.id, title: state.chapter.title, content: state.chapter.content };
10244
- chapterEditorReadOnly = !canEditProse() || !editMode;
10254
+ return null;
10255
+ }
10256
+
10257
+ function chapterRequestStillCurrent(workId, selectionGeneration, selectionRequestId) {
10258
+ return selectionGeneration === chapterSelectionRequestGeneration
10259
+ && selectionRequestId === chapterSelectionRequestId
10260
+ && state.work?.id === workId;
10261
+ }
10262
+
10263
+ function presentChapter(chapter, { editMode = false, contentReady = false, syncRoute = true } = {}) {
10264
+ state.chapter = chapter;
10265
+ if (chapter?.volumeId && typeof chapter.content === "string" && (contentReady || chapter.content.length > 0)) {
10266
+ mergeChapterDirectoryEntry(state.chapter);
10267
+ }
10268
+ if (chapter?.volumeId) state.collapsedVolumeIds.delete(chapter.volumeId);
10269
+ lastSavedChapterSnapshot = {
10270
+ chapterId: state.chapter.id,
10271
+ title: state.chapter.title ?? "",
10272
+ content: state.chapter.content ?? ""
10273
+ };
10274
+ chapterContentReadyId = contentReady ? state.chapter.id : null;
10275
+ chapterEditorReadOnly = !contentReady || !editMode;
10245
10276
  state.module = "editor";
10246
10277
  applyWorkAccessMode();
10247
10278
  markActiveModule("editor");
@@ -10249,22 +10280,77 @@ async function selectChapter(chapterId, { editMode = false } = {}) {
10249
10280
  $("#module-view").classList.add("hidden");
10250
10281
  $("#editor-view").classList.remove("hidden");
10251
10282
  updateChapterPath();
10252
- $("#chapter-title").value = state.chapter.title;
10253
- $("#chapter-content").value = state.chapter.content;
10283
+ const titleInput = $("#chapter-title");
10284
+ const contentInput = $("#chapter-content");
10285
+ const nextTitle = state.chapter.title ?? "";
10286
+ const nextContent = state.chapter.content ?? "";
10287
+ if (titleInput.value !== nextTitle) titleInput.value = nextTitle;
10288
+ if (contentInput.value !== nextContent) contentInput.value = nextContent;
10254
10289
  resetChapterDraftLineIds(state.chapter);
10255
10290
  chapterAnnotationCounts = new Map();
10256
10291
  clearChapterLineSelection();
10257
10292
  scheduleChapterLineNumbers();
10258
- void loadChapterAnnotationCounts(state.chapter.id).catch((error) => {
10259
- if (String(state.chapter?.id ?? "") === String(chapterId)) toast("正文评论数量读取失败,请稍后重试", "error");
10260
- });
10261
10293
  dismissChapterInsightToast();
10262
10294
  updateChapterStats();
10295
+ if (!contentReady) $("#chapter-stats").textContent = "正在载入";
10263
10296
  if (!canEditProse()) setSaveState("正文只读");
10297
+ else if (!contentReady) setSaveState("正在同步正文");
10264
10298
  else if (chapterEditorReadOnly) setSaveState("阅读模式");
10265
10299
  else setSaveState("已保存");
10300
+ setChapterContentSkeleton(!contentReady);
10266
10301
  syncChapterTreeSelection();
10267
- replacePageRoute({ view: "editor", workId: state.work.id, chapterId: state.chapter.id });
10302
+ if (syncRoute) replacePageRoute({ view: "editor", workId: state.work.id, chapterId: state.chapter.id });
10303
+ }
10304
+
10305
+ async function selectChapter(chapterId, { editMode = false } = {}) {
10306
+ imWorkspace.close();
10307
+ const workId = state.work?.id;
10308
+ if (!workId) return false;
10309
+ const sameChapter = String(state.chapter?.id ?? "") === String(chapterId);
10310
+ if (sameChapter && chapterEditingLocked() && chapterBodyAbort && !chapterBodyAbort.signal.aborted) return true;
10311
+ const selectionGeneration = ++chapterSelectionRequestGeneration;
10312
+ const selectionRequestId = ++chapterSelectionRequestId;
10313
+ if (!sameChapter) clearChapterForeshadowReminders({ invalidateRequest: true });
10314
+ if (!sameChapter && !(await confirmDiscardChanges("当前章节有未保存修改,仍要切换吗?"))) {
10315
+ if (selectionRequestId === chapterSelectionRequestId) void loadChapterForeshadowReminders();
10316
+ return false;
10317
+ }
10318
+ resetChapterSearchPanel();
10319
+ dismissDeleteToasts();
10320
+ if (!chapterRequestStillCurrent(workId, selectionGeneration, selectionRequestId)) return false;
10321
+ cancelChapterAutoSave();
10322
+ if (sameChapter && String(chapterContentReadyId ?? "") === String(chapterId) && state.chapter) {
10323
+ presentChapter(state.chapter, { editMode, contentReady: true });
10324
+ syncChapterTreeSelection();
10325
+ return true;
10326
+ }
10327
+ const directoryEntry = directoryChapterEntry(chapterId);
10328
+ const previewChapter = directoryEntry
10329
+ ? { ...directoryEntry, content: "", lineIds: [] }
10330
+ : { id: chapterId, workId, volumeId: "", title: "", content: "", wordCount: 0, versionNo: 0, chapterType: "正文", lineIds: [] };
10331
+ presentChapter(previewChapter, { editMode: false, contentReady: false });
10332
+ syncChapterTreeSelection();
10333
+ chapterBodyAbort?.abort();
10334
+ const chapterBodyController = new AbortController();
10335
+ chapterBodyAbort = chapterBodyController;
10336
+ let selectedChapter = null;
10337
+ try {
10338
+ selectedChapter = await api(`/api/chapters/${encodeURIComponent(chapterId)}`, { signal: chapterBodyController.signal });
10339
+ } catch (error) {
10340
+ if (chapterBodyAbort === chapterBodyController) chapterBodyAbort = null;
10341
+ if (error?.name === "AbortError" || !chapterRequestStillCurrent(workId, selectionGeneration, selectionRequestId)) return false;
10342
+ if (String(state.chapter?.id ?? "") === String(chapterId)) setSaveState("正文载入失败", true);
10343
+ toast(error.message || "章节读取失败", "error");
10344
+ return false;
10345
+ }
10346
+ if (chapterBodyAbort === chapterBodyController) chapterBodyAbort = null;
10347
+ if (!chapterRequestStillCurrent(workId, selectionGeneration, selectionRequestId)) return false;
10348
+ if (String(selectedChapter?.id ?? "") !== String(chapterId) || String(selectedChapter?.workId ?? "") !== String(workId)) return false;
10349
+ presentChapter(selectedChapter, { editMode, contentReady: true, syncRoute: false });
10350
+ syncChapterTreeSelection();
10351
+ void loadChapterAnnotationCounts(state.chapter.id).catch(() => {
10352
+ if (String(state.chapter?.id ?? "") === String(chapterId)) toast("正文评论数量读取失败,请稍后重试", "error");
10353
+ });
10268
10354
  void loadChapterForeshadowReminders();
10269
10355
  return true;
10270
10356
  }
@@ -10836,6 +10922,8 @@ async function showModule(module) {
10836
10922
  state.module = module;
10837
10923
  if (module !== "editor") {
10838
10924
  chapterSelectionRequestId += 1;
10925
+ chapterBodyAbort?.abort();
10926
+ chapterBodyAbort = null;
10839
10927
  clearChapterForeshadowReminders({ invalidateRequest: true });
10840
10928
  }
10841
10929
  if (module !== "tasks") stopTaskProgressRefresh();
@@ -11651,8 +11739,9 @@ function openDraftDialog(item = null, { readOnly = false } = {}) {
11651
11739
  void renderDrafts(moduleListPages.drafts).catch((error) => toast(`想法列表刷新失败:${error instanceof Error ? error.message : "未知错误"}`, "error"));
11652
11740
  });
11653
11741
  $("#draft-dialog-edit")?.addEventListener("click", () => {
11654
- $("#form-dialog").close();
11742
+ // 复用当前模态弹窗,避免旧 close 事件销毁新编辑器。
11655
11743
  openDraftDialog(draftDialogItem);
11744
+ $("#dialog-title-input").focus();
11656
11745
  });
11657
11746
  }
11658
11747
  }
@@ -11897,12 +11986,13 @@ async function renderCharacters(page = characterListPage) {
11897
11986
  || characterFilters.genderValues.length > 0
11898
11987
  || characterFilters.deathState !== "all";
11899
11988
  const pageSize = pageSizeFor("characters");
11989
+ const needsCharacterFilterCatalog = hasCharacterFilters || characterFiltersPanelOpen;
11900
11990
  const [characterSource, races, organizations] = await Promise.all([
11901
11991
  hasCharacterFilters
11902
11992
  ? moduleApiAllPages("characters", `/api/works/${state.work.id}/characters`)
11903
11993
  : moduleApiPage("characters", `/api/works/${state.work.id}/characters`, page, pageSize),
11904
- canReadModule("races") ? moduleApi("characters", `/api/works/${state.work.id}/races`) : Promise.resolve([]),
11905
- canReadModule("organizations") ? moduleApiAllPages("characters", `/api/works/${state.work.id}/organizations`) : Promise.resolve([])
11994
+ needsCharacterFilterCatalog && canReadModule("races") ? moduleApi("characters", `/api/works/${state.work.id}/races`) : Promise.resolve([]),
11995
+ needsCharacterFilterCatalog && canReadModule("organizations") ? moduleApiAllPages("characters", `/api/works/${state.work.id}/organizations`) : Promise.resolve([])
11906
11996
  ]);
11907
11997
  const characterPage = hasCharacterFilters
11908
11998
  ? paginateCharacters(filterCharacters(characterSource, characterFilters), page, pageSize)
@@ -11910,7 +12000,7 @@ async function renderCharacters(page = characterListPage) {
11910
12000
  if (!characterPage.items.length && page > 1) return renderCharacters(page - 1);
11911
12001
  characterListPage = characterPage.page;
11912
12002
  const pageCharacters = characterPage.items;
11913
- [state.races, state.organizations] = [races, organizations];
12003
+ if (needsCharacterFilterCatalog) [state.races, state.organizations] = [races, organizations];
11914
12004
  mountModuleCount(characterPage.total);
11915
12005
  const layout = readModuleLayout();
11916
12006
  const characterActions = (item) => `${characterPinButton(item)}${characterFavoriteButton(item)}${recordCardEditButton("edit-character", item.id, `角色“${item.name}”`)}`;
@@ -14164,10 +14254,7 @@ function updateBackgroundTaskCenterVisibility() {
14164
14254
  $("#background-task-count")?.classList.add("hidden");
14165
14255
  return;
14166
14256
  }
14167
- const activityCount = backgroundTaskActivityCount(
14168
- backgroundTaskCenterSnapshot.taskPage,
14169
- backgroundTaskCenterSnapshot.relationshipIndex
14170
- ) + (productUpdateChecking ? 1 : 0);
14257
+ const activityCount = backgroundTaskActivityCount(backgroundTaskCenterSnapshot.taskPage) + (productUpdateChecking ? 1 : 0);
14171
14258
  const badge = $("#background-task-count");
14172
14259
  badge.textContent = activityCount > 99 ? "99+" : String(activityCount);
14173
14260
  badge.classList.toggle("hidden", activityCount === 0);
@@ -14251,7 +14338,7 @@ function backgroundIndexMarkup(relationshipIndex, error) {
14251
14338
  const editable = canEditModule("ai-settings");
14252
14339
  return `<section class="background-task-section">
14253
14340
  <div class="background-task-section-heading">
14254
- <div><strong>人物关系拼音索引</strong><small>可在任何模块查看增量同步状态</small></div>
14341
+ <div><strong>人物关系拼音索引</strong><small>打开本窗口时显示当前索引状态</small></div>
14255
14342
  <div class="background-index-actions ${editable ? "" : "hidden"}">
14256
14343
  <button class="primary-button" type="button" data-background-index-action="sync">同步增量队列</button>
14257
14344
  <button class="ghost-button" type="button" data-background-index-action="rebuild">完整重建</button>
@@ -14322,10 +14409,7 @@ function scheduleBackgroundTaskCenterRefresh() {
14322
14409
  if (backgroundTaskCenterTimer !== null) window.clearTimeout(backgroundTaskCenterTimer);
14323
14410
  backgroundTaskCenterTimer = null;
14324
14411
  if (!backgroundTaskCenterWorkId) return;
14325
- const activityCount = backgroundTaskActivityCount(
14326
- backgroundTaskCenterSnapshot.taskPage,
14327
- backgroundTaskCenterSnapshot.relationshipIndex
14328
- );
14412
+ const activityCount = backgroundTaskActivityCount(backgroundTaskCenterSnapshot.taskPage);
14329
14413
  const delay = backgroundTaskPollDelay(activityCount, Boolean($("#background-task-dialog")?.open));
14330
14414
  backgroundTaskCenterTimer = window.setTimeout(() => {
14331
14415
  backgroundTaskCenterTimer = null;
@@ -14339,8 +14423,9 @@ async function refreshBackgroundTaskCenter({ announce = true } = {}) {
14339
14423
  if (backgroundTaskCenterTimer !== null) window.clearTimeout(backgroundTaskCenterTimer);
14340
14424
  backgroundTaskCenterTimer = null;
14341
14425
  const requestId = ++backgroundTaskCenterRequest;
14426
+ const dialogOpen = Boolean($("#background-task-dialog")?.open);
14342
14427
  const readTasks = canReadModule("tasks");
14343
- const readIndex = canReadModule("ai-settings");
14428
+ const readIndex = dialogOpen && canReadModule("ai-settings");
14344
14429
  const [taskResult, indexResult] = await Promise.all([
14345
14430
  readTasks
14346
14431
  ? api(`/api/works/${encodeURIComponent(workId)}/tasks?page=1&limit=30`)
@@ -15982,6 +16067,7 @@ async function openDialog(title, fields, onSubmit, eyebrow = "新增", options =
15982
16067
  submit.textContent = options.pendingLabel ?? "处理中…";
15983
16068
  try {
15984
16069
  commitRelationshipKeywordInputs(form);
16070
+ syncVditorEditorValues(form);
15985
16071
  const formData = new FormData(form);
15986
16072
  disabledStates = [...form.elements].map((control) => [control, control.disabled]);
15987
16073
  disabledStates.forEach(([control]) => {
@@ -17392,6 +17478,16 @@ function destroyVditorEditor(editor) {
17392
17478
  if (host) delete host.__vditor;
17393
17479
  }
17394
17480
 
17481
+ function syncVditorEditorValues(container) {
17482
+ // Vditor 的 input 回调有延迟,提交前直接读取正文。
17483
+ container.querySelectorAll("[data-vditor-editor]").forEach((host) => {
17484
+ const valueField = host.parentElement?.querySelector("[data-vditor-value]");
17485
+ const editor = host.__vditor;
17486
+ if (!valueField || valueField.readOnly || typeof editor?.getValue !== "function") return;
17487
+ valueField.value = String(editor.getValue() ?? "");
17488
+ });
17489
+ }
17490
+
17395
17491
  function bindVditorEditors(container) {
17396
17492
  const editors = [];
17397
17493
  container.querySelectorAll("[data-vditor-editor]").forEach((host) => {
@@ -20373,6 +20469,7 @@ async function showVersions() {
20373
20469
  }
20374
20470
  try {
20375
20471
  state.chapter = await api(`/api/chapters/${state.chapter.id}/restore`, { method: "POST", body: { versionNo: Number(button.dataset.restoreVersion) } });
20472
+ chapterContentReadyId = state.chapter.id;
20376
20473
  resetChapterDraftLineIds(state.chapter);
20377
20474
  lastSavedChapterSnapshot = { chapterId: state.chapter.id, title: state.chapter.title, content: state.chapter.content };
20378
20475
  $("#chapter-title").value = state.chapter.title;
@@ -21456,8 +21553,8 @@ $("#login-form").addEventListener("submit", async (event) => {
21456
21553
  window.location.reload();
21457
21554
  } catch (error) {
21458
21555
  $("#auth-error").textContent = error.message;
21459
- // 仅在验证码已显示时自动换一张,未加载过则保持默认隐藏状态
21460
- if (!$("#login-captcha-image").hidden) refreshAuthCaptcha("login").catch(() => {});
21556
+ // 答案错误时保留同一张图;过期、已核销或其他失败才换一张
21557
+ if (error?.code !== "CAPTCHA_INCORRECT" && !$("#login-captcha-image").hidden) refreshAuthCaptcha("login").catch(() => {});
21461
21558
  }
21462
21559
  });
21463
21560
  $("#register-form").addEventListener("submit", async (event) => {
@@ -21485,8 +21582,8 @@ $("#register-form").addEventListener("submit", async (event) => {
21485
21582
  window.location.reload();
21486
21583
  } catch (error) {
21487
21584
  $("#auth-error").textContent = error.message;
21488
- // 仅在验证码已显示时自动换一张,未加载过则保持默认隐藏状态
21489
- if (!$("#register-captcha-image").hidden) refreshAuthCaptcha("register").catch(() => {});
21585
+ // 答案错误时保留同一张图;过期、已核销或其他失败才换一张
21586
+ if (error?.code !== "CAPTCHA_INCORRECT" && !$("#register-captcha-image").hidden) refreshAuthCaptcha("register").catch(() => {});
21490
21587
  }
21491
21588
  });
21492
21589
  $("#settings-return").addEventListener("click", () => returnFromSettings().catch((error) => toast(error.message, "error")));
@@ -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="/mobile-app.css?v=20260922-mobile-offline-v2">
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=ai-composer-compact-controls-v1&feature=ai-context-meter-ring-only-v2&feature=ai-context-cache-hit-v1&feature=annotation-line-counts-v1&feature=chapter-comment-filters-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=ai-stream-character-count-five-digit-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&feature=editor-toolbar-compact-v2&feature=reader-first-frame-v1&feature=auth-compact-v2&feature=editor-preview-toggle-v2&feature=setting-title-chrome-v2&feature=entity-pin-icon-center-v1&feature=chapter-center-bottom-space-v1&feature=work-editor-preferences-v1&feature=work-editor-preference-checkbox-v1&feature=ai-roleplay-memory-v2&feature=ai-roleplay-memory-v3&feature=ai-roleplay-memory-v5&feature=character-editor-header-actions-v1&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=roleplay-memory-pin-border-v1&feature=ai-write-tools-v2&feature=ai-write-card-actions-compact-v1&feature=ai-write-plan-actions-footer-v1&feature=ai-question-actions-footer-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-answer-limit-v1&feature=ai-question-batch-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v1&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=ai-settings-textarea-font-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=ai-skill-slash-menu-v1&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=im-narration-contrast-v1&feature=im-member-add-plus-v2&feature=im-button-hierarchy-v1&feature=im-icon-button-size-v1&feature=compact-sidebar-directory-v5&feature=ai-model-config-dialog-v1&feature=system-prompt-override-v3&feature=entity-editor-back-icon-v1&feature=toast-stack-v1&feature=chapter-header-compact-v4&feature=ai-attachment-disabled-v1&feature=ai-prose-acceptance-removed-v1&feature=chapter-line-scroll-bind-v1&feature=writing-goal-module-navigation-v2&feature=invite-registration-v1&feature=ai-send-mode-v2&feature=character-attributes-prune-v1&feature=chapter-directory-click-priority-v1&feature=annotation-bubble-visibility-v1&feature=chapter-auto-title-menu-v1&feature=chapter-insert-menu-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=ai-composer-compact-controls-v1&feature=ai-context-meter-ring-only-v2&feature=ai-context-cache-hit-v1&feature=annotation-line-counts-v1&feature=chapter-comment-filters-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=ai-stream-character-count-five-digit-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&feature=editor-toolbar-compact-v2&feature=reader-first-frame-v1&feature=auth-compact-v2&feature=editor-preview-toggle-v2&feature=setting-title-chrome-v2&feature=entity-pin-icon-center-v1&feature=chapter-center-bottom-space-v1&feature=work-editor-preferences-v1&feature=work-editor-preference-checkbox-v1&feature=ai-roleplay-memory-v2&feature=ai-roleplay-memory-v3&feature=ai-roleplay-memory-v5&feature=character-editor-header-actions-v1&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=roleplay-memory-pin-border-v1&feature=ai-write-tools-v2&feature=ai-write-card-actions-compact-v1&feature=ai-write-plan-actions-footer-v1&feature=ai-question-actions-footer-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-answer-limit-v1&feature=ai-question-batch-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v1&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=ai-settings-textarea-font-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=ai-skill-slash-menu-v1&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=im-narration-contrast-v1&feature=im-member-add-plus-v2&feature=im-button-hierarchy-v1&feature=im-icon-button-size-v1&feature=compact-sidebar-directory-v5&feature=ai-model-config-dialog-v1&feature=system-prompt-override-v3&feature=entity-editor-back-icon-v1&feature=toast-stack-v1&feature=chapter-header-compact-v4&feature=ai-attachment-disabled-v1&feature=ai-prose-acceptance-removed-v1&feature=chapter-line-scroll-bind-v1&feature=writing-goal-module-navigation-v2&feature=invite-registration-v1&feature=ai-send-mode-v2&feature=character-attributes-prune-v1&feature=chapter-directory-click-priority-v1&feature=annotation-bubble-visibility-v1&feature=chapter-auto-title-menu-v1&feature=chapter-insert-menu-v1&feature=chapter-switch-skeleton-v1">
14
14
  </head>
15
15
  <body class="auth-pending">
16
16
  <section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
@@ -330,6 +330,16 @@
330
330
  <div id="chapter-editor-scroll" class="chapter-editor-scroll">
331
331
  <aside id="chapter-line-numbers" class="chapter-line-numbers" aria-label="正文行号,可拖拽引用多行"><div id="chapter-line-numbers-inner"></div></aside>
332
332
  <textarea id="chapter-content" class="chapter-content" aria-label="章节正文" placeholder="在这里开始你的故事……"></textarea>
333
+ <div id="chapter-content-skeleton" class="chapter-content-skeleton hidden" role="status" aria-live="polite" aria-busy="false" aria-label="正在载入正文">
334
+ <span class="chapter-content-skeleton-line"></span>
335
+ <span class="chapter-content-skeleton-line"></span>
336
+ <span class="chapter-content-skeleton-line"></span>
337
+ <span class="chapter-content-skeleton-line"></span>
338
+ <span class="chapter-content-skeleton-line"></span>
339
+ <span class="chapter-content-skeleton-line"></span>
340
+ <span class="chapter-content-skeleton-line"></span>
341
+ <span class="chapter-content-skeleton-line"></span>
342
+ </div>
333
343
  <div id="chapter-whitespace-overlay" class="chapter-whitespace-overlay is-visible" aria-hidden="true"><div id="chapter-whitespace-inner"></div></div>
334
344
  </div>
335
345
  <div id="chapter-line-measure" class="chapter-line-measure" aria-hidden="true"></div>
@@ -1459,7 +1469,6 @@
1459
1469
  </dialog>
1460
1470
 
1461
1471
  <div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
1462
- <script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-history-rename-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-v2&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&feature=ai-context-input-output-v1&feature=editor-blank-lines-preserved-v1&feature=reader-first-frame-v1&feature=vditor-lazy-load-v1&feature=ui-module-preload-v1&feature=reader-initial-prefetch-v1&feature=editor-preview-toggle-v2&feature=vditor-fullscreen-disabled-v1&feature=chapter-auto-indent-v1&feature=chapter-centered-scroll-v1&feature=work-editor-preferences-v1&feature=ai-context-output-usage-v1&feature=ai-context-meter-ring-only-v1&feature=ai-context-cache-hit-v1&feature=ai-roleplay-memory-v4&feature=ai-roleplay-memory-v5&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-header-toolbar-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=annotation-line-anchor-v1&feature=stable-line-ids-v1&feature=live-annotation-anchors-v1&feature=chapter-comment-filters-v1&feature=ai-write-tools-v3&feature=ai-question-option-supplement-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-continuation-ui-v3&feature=ai-background-stream-v1&feature=ai-question-tool-result-v1&feature=ai-question-tool-summary-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-batch-v1&feature=ai-question-answer-limit-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v3&feature=ai-cancel-preserve-process-v2&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=chapter-batch-editor-refresh-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=semantic-search-rag-label-v1&feature=ai-skill-slash-menu-v1&feature=ai-roleplay-scene-bubble-v1&feature=ai-roleplay-scene-collapse-v1&feature=markdown-adjacent-blockquotes-v1&feature=chapter-save-shortcut-v2&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=ai-fork-progress-toast-v1&feature=im-settings-gear-v1&feature=compact-sidebar-directory-v2&feature=chapter-word-count-consistency-v1&feature=ai-all-message-references-v2&feature=ai-question-render-recovery-v3&feature=setting-category-preservation-v1&feature=toast-stack-v1&feature=toast-stack-label-v2&feature=agent-tool-limits-300-v1&feature=server-dev-logo-v1&feature=chapter-directory-ai-reference-v3&feature=ai-error-origin-v1&feature=ai-stream-render-performance-v2&feature=ai-attachment-disabled-v1&feature=ai-prose-acceptance-removed-v1&feature=ai-inline-message-references-v1&feature=ai-write-plan-status-sync-v1&feature=writing-goal-module-navigation-v2&feature=chapter-line-scroll-bind-v1&feature=invite-registration-v1&feature=ai-send-mode-v2&feature=character-attributes-prune-v1&feature=annotation-note-limit-v1&feature=chapter-directory-performance-v2&feature=chapter-directory-click-priority-v1&feature=mobile-offline-runtime-v6&feature=ai-title-first-turn-v1&feature=ai-optimistic-send-v1&feature=chapter-auto-title-menu-v1&feature=chapter-insert-menu-v1"></script>
1463
-
1472
+ <script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-history-rename-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-v2&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&feature=ai-context-input-output-v1&feature=editor-blank-lines-preserved-v1&feature=reader-first-frame-v1&feature=vditor-lazy-load-v1&feature=ui-module-preload-v1&feature=reader-initial-prefetch-v1&feature=editor-preview-toggle-v2&feature=vditor-fullscreen-disabled-v1&feature=chapter-auto-indent-v1&feature=chapter-centered-scroll-v1&feature=work-editor-preferences-v1&feature=ai-context-output-usage-v1&feature=ai-context-meter-ring-only-v1&feature=ai-context-cache-hit-v1&feature=ai-roleplay-memory-v4&feature=ai-roleplay-memory-v5&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-header-toolbar-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=annotation-line-anchor-v1&feature=stable-line-ids-v1&feature=live-annotation-anchors-v1&feature=chapter-comment-filters-v1&feature=ai-write-tools-v3&feature=ai-question-option-supplement-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-continuation-ui-v3&feature=ai-background-stream-v1&feature=ai-question-tool-result-v1&feature=ai-question-tool-summary-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-batch-v1&feature=ai-question-answer-limit-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v3&feature=ai-cancel-preserve-process-v2&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=chapter-batch-editor-refresh-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=semantic-search-rag-label-v1&feature=ai-skill-slash-menu-v1&feature=ai-roleplay-scene-bubble-v1&feature=ai-roleplay-scene-collapse-v1&feature=markdown-adjacent-blockquotes-v1&feature=chapter-save-shortcut-v2&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=ai-fork-progress-toast-v1&feature=im-settings-gear-v1&feature=compact-sidebar-directory-v2&feature=chapter-word-count-consistency-v1&feature=ai-all-message-references-v2&feature=ai-question-render-recovery-v3&feature=setting-category-preservation-v1&feature=toast-stack-v1&feature=toast-stack-label-v2&feature=agent-tool-limits-300-v1&feature=server-dev-logo-v1&feature=chapter-directory-ai-reference-v3&feature=ai-error-origin-v1&feature=ai-stream-render-performance-v2&feature=ai-attachment-disabled-v1&feature=ai-prose-acceptance-removed-v1&feature=ai-inline-message-references-v1&feature=ai-write-plan-status-sync-v1&feature=writing-goal-module-navigation-v2&feature=chapter-line-scroll-bind-v1&feature=invite-registration-v1&feature=ai-send-mode-v2&feature=character-attributes-prune-v1&feature=annotation-note-limit-v1&feature=chapter-directory-performance-v2&feature=chapter-directory-click-priority-v1&feature=chapter-switch-skeleton-v1&feature=mobile-offline-runtime-v6&feature=ai-title-first-turn-v1&feature=ai-title-xml-v1&feature=ai-optimistic-send-v1&feature=chapter-auto-title-menu-v1&feature=chapter-insert-menu-v1&feature=captcha-retry-same-image-v1&feature=relationship-index-dialog-poll-v1&feature=draft-editor-transition-v2&feature=vditor-submit-sync-v1"></script>
1464
1473
  </body>
1465
1474
  </html>