@musnows/scriverse 0.9.3 → 0.9.5

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.
@@ -0,0 +1,40 @@
1
+ export const CHAPTER_PARAGRAPH_INDENT = "\u3000\u3000";
2
+
3
+ export function insertIndentedParagraph(value, selectionStart, selectionEnd) {
4
+ const text = String(value ?? "");
5
+ const start = Math.max(0, Math.min(text.length, Number(selectionStart) || 0));
6
+ const end = Math.max(start, Math.min(text.length, Number(selectionEnd) || start));
7
+ const insertion = `\n${CHAPTER_PARAGRAPH_INDENT}`;
8
+ const cursor = start + insertion.length;
9
+ return {
10
+ value: `${text.slice(0, start)}${insertion}${text.slice(end)}`,
11
+ selectionStart: cursor,
12
+ selectionEnd: cursor
13
+ };
14
+ }
15
+
16
+ export function chapterLineIndexAtOffset(value, offset) {
17
+ const text = String(value ?? "");
18
+ const safeOffset = Math.max(0, Math.min(text.length, Number(offset) || 0));
19
+ return (text.slice(0, safeOffset).match(/\n/gu) ?? []).length;
20
+ }
21
+
22
+ export function calculateChapterCaretScroll({
23
+ caretBottom,
24
+ scrollTop,
25
+ clientHeight,
26
+ scrollHeight,
27
+ activationRatio = 0.6,
28
+ targetRatio = 0.5
29
+ }) {
30
+ const current = Math.max(0, Number(scrollTop) || 0);
31
+ const viewportHeight = Math.max(0, Number(clientHeight) || 0);
32
+ const contentHeight = Math.max(viewportHeight, Number(scrollHeight) || 0);
33
+ const caretPosition = Math.max(0, Number(caretBottom) || 0);
34
+ if (viewportHeight === 0 || contentHeight <= viewportHeight) return current;
35
+ const activationPoint = current + viewportHeight * activationRatio;
36
+ if (caretPosition <= activationPoint) return current;
37
+ const maximum = Math.max(0, contentHeight - viewportHeight);
38
+ const target = caretPosition - viewportHeight * targetRatio;
39
+ return Math.min(maximum, Math.max(current, target));
40
+ }
@@ -0,0 +1,19 @@
1
+ export type ChapterLineEditHint = {
2
+ selectionStart: number;
3
+ selectionEnd: number;
4
+ inputType?: string;
5
+ };
6
+
7
+ export const MAX_CHAPTER_LINE_IDS: number;
8
+
9
+ export function normalizeChapterLineIdDraft(
10
+ content: unknown,
11
+ lineIds: unknown
12
+ ): Array<string | null>;
13
+
14
+ export function reconcileChapterLineIdDraft(
15
+ beforeContent: unknown,
16
+ afterContent: unknown,
17
+ beforeLineIds: unknown,
18
+ hint?: ChapterLineEditHint | null
19
+ ): Array<string | null>;
@@ -0,0 +1,127 @@
1
+ export const MAX_CHAPTER_LINE_IDS = 100_000;
2
+
3
+ function lineSpans(content) {
4
+ const text = String(content ?? "").replace(/\r\n?/gu, "\n");
5
+ const spans = [];
6
+ let start = 0;
7
+ for (let index = 0; index <= text.length; index += 1) {
8
+ if (index !== text.length && text[index] !== "\n") continue;
9
+ spans.push({ start, end: index });
10
+ start = index + 1;
11
+ }
12
+ return spans;
13
+ }
14
+
15
+ function lineIndexAtOffset(spans, offset) {
16
+ const target = Math.max(0, Number(offset) || 0);
17
+ let low = 0;
18
+ let high = spans.length;
19
+ while (low < high) {
20
+ const middle = Math.floor((low + high) / 2);
21
+ if (spans[middle].start <= target) low = middle + 1;
22
+ else high = middle;
23
+ }
24
+ return Math.max(0, Math.min(spans.length - 1, low - 1));
25
+ }
26
+
27
+ function previousCharacterStart(text, offset) {
28
+ if (offset <= 0) return 0;
29
+ const previous = text.charCodeAt(offset - 1);
30
+ return previous >= 0xdc00 && previous <= 0xdfff && offset >= 2 ? offset - 2 : offset - 1;
31
+ }
32
+
33
+ function nextCharacterEnd(text, offset) {
34
+ if (offset >= text.length) return text.length;
35
+ const current = text.charCodeAt(offset);
36
+ return current >= 0xd800 && current <= 0xdbff && offset + 1 < text.length ? offset + 2 : offset + 1;
37
+ }
38
+
39
+ function hintedChangeRange(beforeContent, afterContent, hint) {
40
+ if (!hint || !Number.isInteger(hint.selectionStart) || !Number.isInteger(hint.selectionEnd)) return null;
41
+ let start = Math.max(0, Math.min(beforeContent.length, hint.selectionStart));
42
+ let end = Math.max(start, Math.min(beforeContent.length, hint.selectionEnd));
43
+ if (start === end && hint.inputType === "deleteContentBackward") start = previousCharacterStart(beforeContent, start);
44
+ if (start === end && hint.inputType === "deleteContentForward") end = nextCharacterEnd(beforeContent, end);
45
+ const insertedLength = afterContent.length - (beforeContent.length - (end - start));
46
+ if (insertedLength < 0) return null;
47
+ const afterEnd = start + insertedLength;
48
+ if (
49
+ afterEnd > afterContent.length
50
+ || beforeContent.slice(0, start) !== afterContent.slice(0, start)
51
+ || beforeContent.slice(end) !== afterContent.slice(afterEnd)
52
+ ) return null;
53
+ return { beforeStart: start, beforeEnd: end, afterStart: start, afterEnd };
54
+ }
55
+
56
+ function inferredChangeRange(beforeContent, afterContent) {
57
+ let prefixLength = 0;
58
+ const maximumPrefix = Math.min(beforeContent.length, afterContent.length);
59
+ while (prefixLength < maximumPrefix && beforeContent[prefixLength] === afterContent[prefixLength]) prefixLength += 1;
60
+ let suffixLength = 0;
61
+ while (
62
+ suffixLength < beforeContent.length - prefixLength
63
+ && suffixLength < afterContent.length - prefixLength
64
+ && beforeContent[beforeContent.length - suffixLength - 1] === afterContent[afterContent.length - suffixLength - 1]
65
+ ) suffixLength += 1;
66
+ return {
67
+ beforeStart: prefixLength,
68
+ beforeEnd: beforeContent.length - suffixLength,
69
+ afterStart: prefixLength,
70
+ afterEnd: afterContent.length - suffixLength
71
+ };
72
+ }
73
+
74
+ export function normalizeChapterLineIdDraft(content, lineIds) {
75
+ const count = lineSpans(content).length;
76
+ if (count > MAX_CHAPTER_LINE_IDS) return [];
77
+ if (!Array.isArray(lineIds) || lineIds.length !== count) return Array.from({ length: count }, () => null);
78
+ const seen = new Set();
79
+ return lineIds.map((lineId) => {
80
+ if (lineId === null) return null;
81
+ if (typeof lineId !== "string" || !lineId || seen.has(lineId)) return null;
82
+ seen.add(lineId);
83
+ return lineId;
84
+ });
85
+ }
86
+
87
+ export function reconcileChapterLineIdDraft(beforeContentValue, afterContentValue, beforeLineIdsValue, hint = null) {
88
+ const beforeContent = String(beforeContentValue ?? "").replace(/\r\n?/gu, "\n");
89
+ const afterContent = String(afterContentValue ?? "").replace(/\r\n?/gu, "\n");
90
+ const beforeSpans = lineSpans(beforeContent);
91
+ const afterSpans = lineSpans(afterContent);
92
+ if (beforeSpans.length > MAX_CHAPTER_LINE_IDS || afterSpans.length > MAX_CHAPTER_LINE_IDS) return [];
93
+ const beforeLineIds = normalizeChapterLineIdDraft(beforeContent, beforeLineIdsValue);
94
+ if (beforeContent === afterContent) return beforeLineIds;
95
+ const change = hintedChangeRange(beforeContent, afterContent, hint)
96
+ ?? inferredChangeRange(beforeContent, afterContent);
97
+ const delta = (change.afterEnd - change.afterStart) - (change.beforeEnd - change.beforeStart);
98
+ const result = Array.from({ length: afterSpans.length }, () => null);
99
+ const affectedLineIds = [];
100
+
101
+ beforeSpans.forEach((span, beforeIndex) => {
102
+ const lineId = beforeLineIds[beforeIndex];
103
+ if (span.end <= change.beforeStart) {
104
+ const afterIndex = lineIndexAtOffset(afterSpans, span.start);
105
+ if (result[afterIndex] === null) result[afterIndex] = lineId;
106
+ return;
107
+ }
108
+ if (span.start >= change.beforeEnd) {
109
+ const afterIndex = lineIndexAtOffset(afterSpans, span.start + delta);
110
+ if (result[afterIndex] === null) result[afterIndex] = lineId;
111
+ return;
112
+ }
113
+ if (lineId !== null) affectedLineIds.push(lineId);
114
+ });
115
+
116
+ const affectedAfterStart = lineIndexAtOffset(afterSpans, change.afterStart);
117
+ const affectedAfterEnd = lineIndexAtOffset(afterSpans, Math.max(change.afterStart, change.afterEnd - 1));
118
+ const availableAfterIndexes = [];
119
+ for (let index = affectedAfterStart; index <= affectedAfterEnd; index += 1) {
120
+ if (result[index] === null) availableAfterIndexes.push(index);
121
+ }
122
+ affectedLineIds.forEach((lineId, index) => {
123
+ const afterIndex = availableAfterIndexes[index];
124
+ if (afterIndex !== undefined) result[afterIndex] = lineId;
125
+ });
126
+ return result;
127
+ }
@@ -6,11 +6,10 @@
6
6
  <meta name="description" content="面向长篇小说创作的 AI 协作工作台">
7
7
  <meta name="theme-color" content="#8b3d2c">
8
8
  <title>叙界 · 小说 AI 创作工作台</title>
9
- <script src="/theme-init.js?v=20260801-work-audit-page-v1"></script>
9
+ <script src="/theme-init.js?v=20260827-reader-prefetch-v1"></script>
10
10
  <link rel="icon" href="/icon.svg?v=20260712" type="image/svg+xml">
11
11
  <link rel="manifest" href="/site.webmanifest">
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=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">
12
+ <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=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=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-header-toolbar-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">
14
13
  </head>
15
14
  <body class="auth-pending">
16
15
  <section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
@@ -251,12 +250,10 @@
251
250
  <input id="chapter-title" class="chapter-title" aria-label="章节标题" placeholder="章节标题">
252
251
  <div class="editor-actions">
253
252
  <span id="chapter-stats" class="chapter-stats">0 字 · v0</span>
254
- <button id="chapter-reader-button" class="ghost-button" type="button">阅读预览</button>
255
253
  <button id="insight-button" class="ghost-button" type="button" aria-controls="chapter-insight-toast" aria-expanded="false">章节概览</button>
256
254
  <button id="versions-button" class="ghost-button" type="button">版本</button>
257
255
  <button id="chapter-annotations-button" class="ghost-button" type="button">评论</button>
258
- <button id="chapter-delete-button" class="danger-button" type="button">删除章节</button>
259
- <button id="chapter-edit-button" class="primary-button hidden" type="button">编辑</button>
256
+ <button id="chapter-edit-button" class="primary-button hidden" type="button" aria-pressed="false" aria-label="切换到编辑模式">编辑</button>
260
257
  <button id="tidy-blank-lines-button" class="ghost-button" type="button">整理空行</button>
261
258
  <button id="save-button" class="primary-button" type="button">保存正文</button>
262
259
  </div>
@@ -333,6 +330,7 @@
333
330
  <div class="ai-heading-actions">
334
331
  <button id="ai-conversation-switcher" class="ai-conversation-switcher" type="button" aria-label="切换打开的对话" aria-controls="ai-conversation-switcher-menu" aria-expanded="false" aria-haspopup="dialog" title="切换打开的对话"><span id="ai-conversation-title">新对话</span><small id="ai-open-conversation-count">1</small><svg viewBox="0 0 12 8" aria-hidden="true" focusable="false"><path d="M1 1.5 6 6.5l5-5"></path></svg></button>
335
332
  <button id="ai-history-toggle" type="button" aria-label="历史记录" aria-controls="ai-history-dialog" aria-expanded="false" aria-haspopup="dialog" title="历史记录"><svg class="ai-heading-action-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"></path><path d="M3 3v5h5M12 7v5l3.5 2"></path></svg></button>
333
+ <button id="ai-approval-center-toggle" type="button" aria-label="AI 操作审批中心" aria-controls="ai-approval-center-dialog" aria-expanded="false" aria-haspopup="dialog" title="AI 操作审批中心"><svg class="ai-heading-action-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 3l7 3v5c0 4.4-2.9 8.2-7 10-4.1-1.8-7-5.6-7-10V6z"></path><path d="M9 12l2.2 2.2L15.5 9.5"></path></svg></button>
336
334
  <button id="ai-new-conversation" type="button" aria-label="新建对话" title="新建对话">+</button>
337
335
  </div>
338
336
  </div>
@@ -463,7 +461,7 @@
463
461
  <div id="reader-page-shell" class="reader-page-shell">
464
462
  <div id="reader-content" class="reader-content"></div>
465
463
  </div>
466
- <div id="reader-continuation" class="reader-continuation">
464
+ <div id="reader-continuation" class="reader-continuation hidden">
467
465
  <button id="reader-continue" class="ghost-button" type="button">继续下一章</button>
468
466
  </div>
469
467
  </article>
@@ -702,6 +700,7 @@
702
700
  <button type="button" role="tab" data-character-editor-tab="settings" aria-selected="false">扩展设定<small>属性与长篇章节</small></button>
703
701
  <button type="button" role="tab" data-character-editor-tab="state" aria-selected="false">状态与约束<small>当前状态与锁定项</small></button>
704
702
  <button type="button" role="tab" data-character-editor-tab="relationships" aria-selected="false">人物关系<small>关联人物与关系关键词</small></button>
703
+ <button type="button" role="tab" data-character-editor-tab="roleplay-memory" aria-selected="false">角色扮演记忆<small>该角色的作品内共享记忆库</small></button>
705
704
  </nav>
706
705
  <main id="character-editor-fields" class="character-editor-fields"></main>
707
706
  <aside id="character-history-panel" class="character-history-panel hidden" aria-label="人物版本历史">
@@ -1229,6 +1228,58 @@
1229
1228
  </div>
1230
1229
  </dialog>
1231
1230
 
1231
+ <dialog id="ai-write-plan-dialog" class="dialog wide-dialog ai-write-plan-dialog" aria-labelledby="ai-write-plan-title">
1232
+ <div class="dialog-header">
1233
+ <div><span class="eyebrow">写入审批</span><h2 id="ai-write-plan-title">AI 修改计划 · 完整明细</h2></div>
1234
+ <button id="ai-write-plan-close" class="dialog-close" aria-label="关闭审批详情" type="button">×</button>
1235
+ </div>
1236
+ <div id="ai-write-plan-body" class="ai-write-plan-body" aria-live="polite"></div>
1237
+ <div class="card-actions ai-plan-actions">
1238
+ <button id="ai-write-plan-refresh" class="ghost-button" type="button">刷新状态</button>
1239
+ <button id="ai-write-plan-reject" class="ghost-button" type="button">拒绝</button>
1240
+ <button id="ai-write-plan-undo" class="ghost-button hidden" type="button">撤销本次审批</button>
1241
+ <button id="ai-write-plan-confirm" class="primary-button" type="button">确认执行</button>
1242
+ </div>
1243
+ </dialog>
1244
+
1245
+ <dialog id="ai-question-dialog" class="dialog ai-question-dialog" aria-labelledby="ai-question-title">
1246
+ <div class="dialog-header">
1247
+ <div><span class="eyebrow">AI 提问</span><h2 id="ai-question-title">需要你的回答</h2></div>
1248
+ <button id="ai-question-close" class="dialog-close" aria-label="关闭提问" type="button">×</button>
1249
+ </div>
1250
+ <div class="ai-question-body">
1251
+ <p id="ai-question-text" class="ai-question-text"></p>
1252
+ <form id="ai-question-form">
1253
+ <div id="ai-question-options" class="ai-question-options" role="radiogroup" aria-label="预设选项"></div>
1254
+ <label class="ai-question-custom-field"><span class="field-label ai-question-custom-heading"><span>自定义回答 / 补充信息</span><output id="ai-question-answer-count" for="ai-question-custom-answer" aria-live="polite">已填写 0 / 3000</output></span><input id="ai-question-custom-answer" type="text" maxlength="3000" aria-describedby="ai-question-answer-count" placeholder="可补充所选方案;未选预设时作为自定义回答" autocomplete="off"></label>
1255
+ <p id="ai-question-expiry" class="usage-measurement-note" role="status"></p>
1256
+ </form>
1257
+ </div>
1258
+ <div class="card-actions ai-question-actions">
1259
+ <button id="ai-question-skip" class="ghost-button" type="button">暂不回答</button>
1260
+ <button id="ai-question-submit" class="primary-button" type="button" disabled>提交回答</button>
1261
+ </div>
1262
+ </dialog>
1263
+
1264
+ <dialog id="ai-approval-center-dialog" class="dialog wide-dialog ai-approval-center-dialog" aria-labelledby="ai-approval-center-title">
1265
+ <div class="dialog-header">
1266
+ <div><span class="eyebrow">审批工作流</span><h2 id="ai-approval-center-title">AI 操作审批中心</h2></div>
1267
+ <button id="ai-approval-center-close" class="dialog-close" aria-label="关闭审批中心" type="button">×</button>
1268
+ </div>
1269
+ <div class="ai-approval-center-body">
1270
+ <div class="ai-approval-filters" role="group" aria-label="按状态筛选审批记录">
1271
+ <button type="button" data-status-filter="" aria-pressed="true">全部</button>
1272
+ <button type="button" data-status-filter="pending" aria-pressed="false">待确认</button>
1273
+ <button type="button" data-status-filter="executed" aria-pressed="false">执行成功</button>
1274
+ <button type="button" data-status-filter="rejected" aria-pressed="false">已拒绝</button>
1275
+ <button type="button" data-status-filter="invalidated" aria-pressed="false">已失效</button>
1276
+ <button type="button" data-status-filter="expired" aria-pressed="false">已过期</button>
1277
+ <button type="button" data-status-filter="failed" aria-pressed="false">执行失败</button>
1278
+ </div>
1279
+ <div id="ai-approval-list-host" class="ai-approval-list-host" aria-live="polite"></div>
1280
+ </div>
1281
+ </dialog>
1282
+
1232
1283
  <dialog id="relationship-map-dialog" class="relationship-map-dialog" aria-label="放大人物关系图" data-testid="relationship-map-expanded">
1233
1284
  <button id="relationship-map-close" class="relationship-map-floating-close" aria-label="关闭放大关系图" type="button">×</button>
1234
1285
  <div id="relationship-map-expanded-host" class="relationship-map-expanded-host"></div>
@@ -1260,8 +1311,6 @@
1260
1311
  </dialog>
1261
1312
 
1262
1313
  <div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
1263
- <script id="vditorIconScript" src="/vendor/vditor/dist/js/icons/ant.js?v=3.11.2"></script>
1264
- <script src="/vendor/vditor/dist/index.min.js?v=3.11.2"></script>
1265
- <script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v2&feature=analysis-task-queue-refresh-v1&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=ai-session-id-copy-v2&feature=galaxy-motion-mode-v3&feature=galaxy-edge-label-threshold-v1&feature=calculate-time-tool-v2&feature=analysis-task-expired-toast-v1&feature=global-replace-volume-v1&feature=chapter-search-replace-v1&feature=chapter-save-toast-v1&feature=character-relationship-delete-v1&feature=character-relationship-group-v1&feature=analysis-task-stability-delay-v1&feature=ai-assistant-workspace-v2&feature=volume-detail-icon-v1&feature=volume-story-order-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-v1&feature=ai-roleplay-message-reference-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v3&feature=annotation-permissions-v1&feature=annotation-line-counts-v1&feature=ai-relationship-roleplay-v1&feature=ai-roleplay-user-character-visibility-v1&feature=ai-roleplay-story-recall-v1&feature=ai-model-picker-v1&feature=ai-fork-model-unlock-v1&feature=context-percent-format-v1&feature=annotation-precise-locate-v1&feature=markdown-word-count-stable-v1&feature=ai-stream-character-count-stable-v2&feature=ai-process-empty-intermediate-v1&feature=ai-feed-scroll-follow-v1&feature=ai-message-retry-v1&feature=ai-stream-idle-timeout-v2&feature=ai-config-delete-v1&feature=ai-provider-protocol-options-v1&feature=ai-provider-thinking-type-v1&feature=ai-chat-image-attachments-v8&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=ai-image-conversation-model-lock-v1&feature=toast-click-dismiss-v2&feature=system-restart-dialog-delay-v1&feature=character-avatar-v6&feature=character-death-position-v1&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-usage-pricing-label-v1&feature=ai-usage-token-breakdown-v1&feature=ai-usage-pricing-cache-v2&feature=ai-usage-pricing-manual-refresh-v1&feature=ai-monthly-token-quota-v1&feature=ai-provider-token-quota-v1&feature=ai-token-quota-positive-v6&feature=ai-model-thinking-label-v1&feature=ai-model-picker-focus-v1&feature=ai-provider-model-import-v1&feature=ai-assistant-brain-icon-v1&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-token-usage-raw-input-v1&feature=ai-stream-connection-seconds-v1&feature=phone-client-entry-v1&feature=ai-usage-pricing-cache-v1&feature=ai-model-thinking-label-v3&feature=ai-roleplay-speaker-label-v1&feature=toast-modal-host-v1&feature=api-key-copy-existing-v1&feature=character-favorite-v1&feature=record-favorites-v1&feature=ai-token-usage-estimated-price-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=entity-pin-icon-v1&feature=roleplay-favorite-label-v1&feature=ai-roleplay-knowledge-tools-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v2&feature=admin-account-identity-v2&feature=ai-provider-analysis-timeout-v1&feature=task-detail-failure-orange-v1&feature=book-import-progress-v1&feature=presence-multiple-users-v1"></script>
1314
+ <script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v2&feature=analysis-task-queue-refresh-v1&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=ai-session-id-copy-v2&feature=galaxy-motion-mode-v3&feature=galaxy-edge-label-threshold-v1&feature=calculate-time-tool-v2&feature=analysis-task-expired-toast-v1&feature=global-replace-volume-v1&feature=chapter-search-replace-v1&feature=chapter-save-toast-v1&feature=character-relationship-delete-v1&feature=character-relationship-group-v1&feature=analysis-task-stability-delay-v1&feature=ai-assistant-workspace-v2&feature=volume-detail-icon-v1&feature=volume-story-order-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-v1&feature=ai-roleplay-message-reference-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v3&feature=annotation-permissions-v1&feature=annotation-line-counts-v1&feature=ai-relationship-roleplay-v1&feature=ai-roleplay-user-character-visibility-v1&feature=ai-roleplay-story-recall-v1&feature=ai-model-picker-v1&feature=ai-fork-model-unlock-v1&feature=context-percent-format-v1&feature=annotation-precise-locate-v1&feature=markdown-word-count-stable-v1&feature=ai-stream-character-count-stable-v2&feature=ai-process-empty-intermediate-v1&feature=ai-feed-scroll-follow-v1&feature=ai-message-retry-v1&feature=ai-stream-idle-timeout-v2&feature=ai-config-delete-v1&feature=ai-provider-protocol-options-v1&feature=ai-provider-thinking-type-v1&feature=ai-chat-image-attachments-v8&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=ai-image-conversation-model-lock-v1&feature=toast-click-dismiss-v2&feature=system-restart-dialog-delay-v1&feature=character-avatar-v6&feature=character-death-position-v1&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-usage-pricing-label-v1&feature=ai-usage-token-breakdown-v1&feature=ai-usage-pricing-cache-v2&feature=ai-usage-pricing-manual-refresh-v1&feature=ai-monthly-token-quota-v1&feature=ai-provider-token-quota-v1&feature=ai-token-quota-positive-v6&feature=ai-model-thinking-label-v1&feature=ai-model-picker-focus-v1&feature=ai-provider-model-import-v1&feature=ai-assistant-brain-icon-v1&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-token-usage-raw-input-v1&feature=ai-stream-connection-seconds-v1&feature=phone-client-entry-v1&feature=ai-usage-pricing-cache-v1&feature=ai-model-thinking-label-v3&feature=ai-roleplay-speaker-label-v1&feature=toast-modal-host-v1&feature=api-key-copy-existing-v1&feature=character-favorite-v1&feature=record-favorites-v1&feature=ai-token-usage-estimated-price-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=entity-pin-icon-v1&feature=roleplay-favorite-label-v1&feature=ai-roleplay-knowledge-tools-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v2&feature=admin-account-identity-v2&feature=ai-provider-analysis-timeout-v1&feature=task-detail-failure-orange-v1&feature=book-import-progress-v1&feature=presence-multiple-users-v1&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-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=ai-write-tools-v3&feature=ai-question-option-supplement-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-continuation-ui-v1&feature=ai-question-tool-result-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-answer-limit-v1"></script>
1266
1315
  </body>
1267
1316
  </html>