@musnows/scriverse 0.8.0 → 0.8.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.js +22 -2
- package/dist/ai.js.map +1 -1
- package/dist/app.js +39 -10
- package/dist/app.js.map +1 -1
- package/dist/public/app.js +161 -53
- package/dist/public/index.html +4 -4
- package/dist/public/stream-typewriter.d.ts +1 -0
- package/dist/public/stream-typewriter.js +12 -0
- package/dist/public/styles.css +19 -13
- package/dist/public/work-permissions.d.ts +1 -1
- package/dist/public/work-permissions.js +13 -1
- package/dist/store.js +50 -10
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +34 -7
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/work-permissions.js +13 -2
- package/dist/work-permissions.js.map +1 -1
- package/package.json +1 -1
package/dist/public/app.js
CHANGED
|
@@ -20,7 +20,7 @@ import { MIN_MODEL_CONTEXT_WINDOW, MODEL_PURPOSE_OPTIONS, MODEL_THINKING_EFFORT_
|
|
|
20
20
|
import { connectivityConfigurationSavedToast, connectivityTestErrorToast, connectivityTestResultToast } from "/ai-connectivity-test.js?v=20260812-connectivity-cooldown-v1";
|
|
21
21
|
import { shouldSendAiPrompt } from "/ai-prompt-keyboard.js?v=20260713-enter-to-send";
|
|
22
22
|
import { estimateAiMessageTokens, formatAiMessageMeta } from "/ai-message-meta.js?v=20260814-ai-model-lock-v1";
|
|
23
|
-
import { createStreamTypewriter, createStreamTypewriterSpeedController } from "/stream-typewriter.js?v=
|
|
23
|
+
import { createStreamTypewriter, createStreamTypewriterSpeedController } from "/stream-typewriter.js?v=20260818-ai-agent-turn-process-v1";
|
|
24
24
|
import { assertAiStreamCompleted, readAiEventStream } from "/ai-stream-protocol.js?v=20260812-ai-stream-complete-v1";
|
|
25
25
|
import { buildUsageCalendar, formatCacheHitRate, formatTokenCount } from "/ai-usage.js?v=20260727-ai-usage-v1";
|
|
26
26
|
import { formatAiMessageTime } from "/ai-message-time.js?v=20260801-month-day-time";
|
|
@@ -73,7 +73,7 @@ import { splitRelationshipKeywordInput, splitRelationshipKeywords, uniqueRelatio
|
|
|
73
73
|
import { tokenizeVisibleSpaces } from "/whitespace-visualization.js?v=20260718-visible-whitespace";
|
|
74
74
|
import { buildRaceForest, eligibleRaceParents, orderRaceFilterOptions, racePathLabel } from "/race-hierarchy.js?v=20260729-race-tree-all-v1";
|
|
75
75
|
import { ANALYSIS_TYPES, analysisTypeDescription } from "/analysis-types.js?v=20260721-analysis-descriptions";
|
|
76
|
-
import { WORK_PERMISSION_MODULES, canReadPermissionModule, canReadUiModule, canWritePermissionModule, canWriteUiModule, emptyModulePermissions, firstReadableUiModule, normalizeModulePermissions, permissionSummary } from "/work-permissions.js?v=
|
|
76
|
+
import { WORK_PERMISSION_MODULES, canReadPermissionModule, canReadUiModule, canWritePermissionModule, canWriteUiModule, emptyModulePermissions, firstReadableUiModule, normalizeModulePermissions, permissionSummary } from "/work-permissions.js?v=20260818-annotation-permissions-v1";
|
|
77
77
|
import { MODULE_LAYOUT_STORAGE_KEY, LEGACY_SETTINGS_LAYOUT_STORAGE_KEY, normalizeModuleLayout } from "/module-layout.js?v=20260723-module-layout-toggle";
|
|
78
78
|
import { isGlobalSearchShortcut } from "/keyboard-shortcuts.js?v=20260723-global-search";
|
|
79
79
|
import { prioritizeGlobalSearchResults, resolveGlobalSearchTarget, splitGlobalSearchHighlight } from "/global-search.js?v=20260804-agent-history-score-sort-v1";
|
|
@@ -230,6 +230,8 @@ let systemBootCheckTimer = null;
|
|
|
230
230
|
let systemBootCheckPromise = null;
|
|
231
231
|
let systemRestartDetected = false;
|
|
232
232
|
let chapterAnnotations = [];
|
|
233
|
+
let chapterAnnotationCounts = new Map();
|
|
234
|
+
let chapterAnnotationsLineIndex = null;
|
|
233
235
|
let workAuditRecords = [];
|
|
234
236
|
let workAuditNextPage = null;
|
|
235
237
|
const chapterBatchSelectedIds = new Set();
|
|
@@ -376,6 +378,13 @@ function canEditProse(work = state.work) {
|
|
|
376
378
|
return canWriteUiModule(work, "editor");
|
|
377
379
|
}
|
|
378
380
|
|
|
381
|
+
function canManageChapterAnnotation(annotation, work = state.work) {
|
|
382
|
+
if (!work) return false;
|
|
383
|
+
if (annotation.kind !== "todo") return canEditProse(work);
|
|
384
|
+
if (["admin", "owner"].includes(String(work.accessRole))) return true;
|
|
385
|
+
return annotation.createdByUserId === state.user?.userId && canWritePermissionModule(work, "todos");
|
|
386
|
+
}
|
|
387
|
+
|
|
379
388
|
function canReplaceProse(work = state.work) {
|
|
380
389
|
return WORK_PERMISSION_MODULES
|
|
381
390
|
.filter((item) => item.id !== "ai-settings")
|
|
@@ -1334,9 +1343,9 @@ function applyPanelLayout(persist = false) {
|
|
|
1334
1343
|
? (panelLayout.leftCollapsed ? "打开作品模块" : "关闭作品模块")
|
|
1335
1344
|
: (panelLayout.leftCollapsed ? "展开作品侧栏" : "收起作品侧栏"));
|
|
1336
1345
|
$("#mobile-module-tab").setAttribute("aria-expanded", String(!panelLayout.leftCollapsed));
|
|
1337
|
-
$("#ai-panel-toggle").textContent = panelLayout.aiCollapsed ? "‹" : "›";
|
|
1346
|
+
$("#ai-panel-toggle").textContent = aiConversationWorkspaceOpen ? "×" : (panelLayout.aiCollapsed ? "‹" : "›");
|
|
1338
1347
|
$("#ai-panel-toggle").setAttribute("aria-expanded", String(!panelLayout.aiCollapsed));
|
|
1339
|
-
$("#ai-panel-toggle").setAttribute("aria-label", panelLayout.aiCollapsed ? "展开创作助手" : "收起创作助手");
|
|
1348
|
+
$("#ai-panel-toggle").setAttribute("aria-label", aiConversationWorkspaceOpen ? "关闭创作助手全屏面板" : (panelLayout.aiCollapsed ? "展开创作助手" : "收起创作助手"));
|
|
1340
1349
|
syncMobileAiPanelSafeTop();
|
|
1341
1350
|
scheduleChapterLineNumbers();
|
|
1342
1351
|
if (persist) {
|
|
@@ -1346,7 +1355,12 @@ function applyPanelLayout(persist = false) {
|
|
|
1346
1355
|
|
|
1347
1356
|
function ensureAiPanelExpanded() {
|
|
1348
1357
|
if (!state.work || !canReadPermissionModule(state.work, "ai-chat")) return;
|
|
1349
|
-
|
|
1358
|
+
if (isMobileViewport()) {
|
|
1359
|
+
setAiConversationWorkspaceVisible(true);
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
panelLayout.aiCollapsed = false;
|
|
1363
|
+
applyPanelLayout(true);
|
|
1350
1364
|
}
|
|
1351
1365
|
|
|
1352
1366
|
function setupPanelResize(handle, side) {
|
|
@@ -1496,7 +1510,7 @@ function applyChapterEditorMode() {
|
|
|
1496
1510
|
$("#chapter-content").setAttribute("aria-readonly", String(viewOnly));
|
|
1497
1511
|
$("#chapter-edit-button").classList.toggle("hidden", permissionBlocked || !chapterEditorReadOnly || !state.chapter);
|
|
1498
1512
|
$("#chapter-delete-button").classList.toggle("hidden", permissionBlocked || chapterEditorReadOnly || !state.chapter);
|
|
1499
|
-
$("#chapter-annotations-button").classList.toggle("hidden", !state.chapter);
|
|
1513
|
+
$("#chapter-annotations-button").classList.toggle("hidden", !state.chapter || !canReadModule("comments"));
|
|
1500
1514
|
$("#chapter-reader-button").classList.toggle("hidden", !state.chapter || !canReadModule("editor"));
|
|
1501
1515
|
syncChapterSearchControls();
|
|
1502
1516
|
if (viewOnly) cancelChapterAutoSave();
|
|
@@ -1788,22 +1802,36 @@ function renderChapterLineNumbers({ targetLineIndex = null } = {}) {
|
|
|
1788
1802
|
);
|
|
1789
1803
|
const numbers = document.createDocumentFragment();
|
|
1790
1804
|
for (let index = lineWindow.start; index < lineWindow.end; index += 1) {
|
|
1791
|
-
const number = document.createElement("
|
|
1792
|
-
number.type = "button";
|
|
1805
|
+
const number = document.createElement("div");
|
|
1793
1806
|
number.className = "chapter-line-number";
|
|
1794
|
-
number.textContent = String(index + 1);
|
|
1795
1807
|
number.dataset.lineIndex = String(index);
|
|
1796
|
-
|
|
1797
|
-
|
|
1808
|
+
const lineSelect = document.createElement("button");
|
|
1809
|
+
lineSelect.type = "button";
|
|
1810
|
+
lineSelect.className = "chapter-line-number-select";
|
|
1811
|
+
lineSelect.textContent = String(index + 1);
|
|
1812
|
+
lineSelect.setAttribute("aria-label", `选择第 ${index + 1} 行`);
|
|
1813
|
+
lineSelect.tabIndex = -1;
|
|
1814
|
+
number.append(lineSelect);
|
|
1815
|
+
const annotationCount = chapterAnnotationCounts.get(index + 1) ?? 0;
|
|
1816
|
+
if (annotationCount > 0) {
|
|
1817
|
+
const bubble = document.createElement("button");
|
|
1818
|
+
bubble.type = "button";
|
|
1819
|
+
bubble.className = "chapter-line-annotation-count";
|
|
1820
|
+
bubble.dataset.lineIndex = String(index);
|
|
1821
|
+
bubble.dataset.lineAnnotationCount = String(annotationCount);
|
|
1822
|
+
bubble.textContent = String(annotationCount);
|
|
1823
|
+
bubble.setAttribute("aria-label", `查看第 ${index + 1} 行的 ${annotationCount} 条评论和待办`);
|
|
1824
|
+
number.append(bubble);
|
|
1825
|
+
}
|
|
1798
1826
|
const selected = chapterLineSelection && index >= chapterLineSelection.start && index <= chapterLineSelection.end;
|
|
1799
1827
|
if (selected) {
|
|
1800
1828
|
number.classList.add("is-line-selected");
|
|
1801
|
-
|
|
1829
|
+
lineSelect.setAttribute("aria-pressed", "true");
|
|
1802
1830
|
}
|
|
1803
1831
|
const bounds = getLineBounds(index);
|
|
1804
1832
|
number.style.top = `${bounds.top}px`;
|
|
1805
1833
|
number.style.height = `${bounds.bottom - bounds.top}px`;
|
|
1806
|
-
|
|
1834
|
+
lineSelect.style.paddingTop = `${numberTextOffset}px`;
|
|
1807
1835
|
numbers.append(number);
|
|
1808
1836
|
}
|
|
1809
1837
|
inner.replaceChildren(numbers);
|
|
@@ -1879,7 +1907,7 @@ function paintChapterLineSelection(anchor, focus) {
|
|
|
1879
1907
|
$("#chapter-line-numbers-inner").querySelectorAll(".chapter-line-number").forEach((row) => {
|
|
1880
1908
|
const selected = Number(row.dataset.lineIndex) >= start && Number(row.dataset.lineIndex) <= end;
|
|
1881
1909
|
row.classList.toggle("is-line-selected", selected);
|
|
1882
|
-
row.setAttribute("aria-pressed", String(selected));
|
|
1910
|
+
row.querySelector(".chapter-line-number-select")?.setAttribute("aria-pressed", String(selected));
|
|
1883
1911
|
});
|
|
1884
1912
|
}
|
|
1885
1913
|
|
|
@@ -2187,20 +2215,21 @@ function setAiConversationSwitcherVisible(visible) {
|
|
|
2187
2215
|
}
|
|
2188
2216
|
|
|
2189
2217
|
function setAiConversationWorkspaceVisible(visible) {
|
|
2218
|
+
const mobileWorkspace = isMobileViewport();
|
|
2190
2219
|
aiConversationWorkspaceOpen = Boolean(visible);
|
|
2191
|
-
|
|
2192
|
-
panelLayout.aiCollapsed = false;
|
|
2193
|
-
$("#app").classList.remove("ai-panel-collapsed");
|
|
2194
|
-
}
|
|
2220
|
+
panelLayout.aiCollapsed = aiConversationWorkspaceOpen ? false : mobileWorkspace;
|
|
2195
2221
|
$("#app").classList.toggle("ai-workspace-mode", aiConversationWorkspaceOpen);
|
|
2196
2222
|
$(".ai-panel").classList.toggle("is-conversation-workspace", aiConversationWorkspaceOpen);
|
|
2197
2223
|
$("#ai-assistant-entry").classList.toggle("active", aiConversationWorkspaceOpen);
|
|
2198
2224
|
$("#ai-assistant-entry").setAttribute("aria-expanded", String(aiConversationWorkspaceOpen));
|
|
2225
|
+
applyPanelLayout(true);
|
|
2199
2226
|
$("#ai-chat-tabs").classList.add("hidden");
|
|
2200
2227
|
$("#ai-workspace-close").classList.toggle("hidden", !aiConversationWorkspaceOpen);
|
|
2201
2228
|
$("#ai-panel-resize").setAttribute("aria-hidden", String(aiConversationWorkspaceOpen));
|
|
2202
2229
|
setAiConversationSwitcherVisible(false);
|
|
2203
2230
|
renderAiChatTabs();
|
|
2231
|
+
$("#ai-panel-toggle").textContent = aiConversationWorkspaceOpen ? "×" : (panelLayout.aiCollapsed ? "‹" : "›");
|
|
2232
|
+
$("#ai-panel-toggle").setAttribute("aria-label", aiConversationWorkspaceOpen ? "关闭创作助手全屏面板" : (panelLayout.aiCollapsed ? "展开创作助手" : "收起创作助手"));
|
|
2204
2233
|
if (aiConversationWorkspaceOpen) {
|
|
2205
2234
|
window.requestAnimationFrame(() => $("#ai-prompt").focus({ preventScroll: true }));
|
|
2206
2235
|
}
|
|
@@ -2411,16 +2440,18 @@ function renderMessageCardActions(message) {
|
|
|
2411
2440
|
message.append(actions);
|
|
2412
2441
|
}
|
|
2413
2442
|
actions.replaceChildren();
|
|
2414
|
-
|
|
2443
|
+
const hasCopyValue = Object.hasOwn(message.dataset, "rawMarkdown") || Object.hasOwn(message.dataset, "copyText");
|
|
2444
|
+
if (hasCopyValue) {
|
|
2415
2445
|
const copy = document.createElement("button");
|
|
2416
2446
|
copy.type = "button";
|
|
2417
2447
|
copy.className = "message-copy-button";
|
|
2418
|
-
|
|
2448
|
+
const isUserMessage = message.classList.contains("user-message");
|
|
2449
|
+
copy.setAttribute("aria-label", isUserMessage ? "复制用户指令" : "复制 AI 回复");
|
|
2419
2450
|
copy.innerHTML = '<svg class="message-action-icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2"/><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"/></svg><span>复制</span>';
|
|
2420
2451
|
const copyLabel = copy.querySelector("span");
|
|
2421
2452
|
copy.addEventListener("click", async () => {
|
|
2422
2453
|
try {
|
|
2423
|
-
await copyAiRawMarkdown(message.dataset.rawMarkdown);
|
|
2454
|
+
await copyAiRawMarkdown(message.dataset.rawMarkdown ?? message.dataset.copyText ?? "");
|
|
2424
2455
|
copyLabel.textContent = "已复制";
|
|
2425
2456
|
window.setTimeout(() => { copyLabel.textContent = "复制"; }, 1200);
|
|
2426
2457
|
} catch (error) {
|
|
@@ -2470,6 +2501,11 @@ function attachAssistantCopyAction(message, rawMarkdown) {
|
|
|
2470
2501
|
renderMessageCardActions(message);
|
|
2471
2502
|
}
|
|
2472
2503
|
|
|
2504
|
+
function attachUserCopyAction(message, text) {
|
|
2505
|
+
message.dataset.copyText = String(text ?? "");
|
|
2506
|
+
renderMessageCardActions(message);
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2473
2509
|
function attachMessageIdentity(message, messageId) {
|
|
2474
2510
|
if (!messageId) return;
|
|
2475
2511
|
message.dataset.messageId = messageId;
|
|
@@ -3576,7 +3612,8 @@ function addSelectedLinesAsCitation() {
|
|
|
3576
3612
|
}
|
|
3577
3613
|
|
|
3578
3614
|
async function createSelectedLineAnnotation(kind) {
|
|
3579
|
-
|
|
3615
|
+
const permissionModule = kind === "todo" ? "todos" : "comments";
|
|
3616
|
+
if (!state.chapter || !chapterLineSelection || !canWritePermissionModule(state.work, permissionModule)) return;
|
|
3580
3617
|
const selection = selectedChapterLinePayload(chapterLineSelection.start, chapterLineSelection.end);
|
|
3581
3618
|
closeLineCitationMenu();
|
|
3582
3619
|
const note = await inputToast(kind === "todo" ? "描述需要后续处理的事项" : `评论第 ${selection.safeStart + 1} 行正文`, {
|
|
@@ -3592,7 +3629,8 @@ async function createSelectedLineAnnotation(kind) {
|
|
|
3592
3629
|
body: { kind, startLine: selection.safeStart + 1, endLine: selection.safeEnd + 1, note }
|
|
3593
3630
|
});
|
|
3594
3631
|
toast(kind === "todo" ? "正文待办已添加" : "正文评论已添加");
|
|
3595
|
-
await
|
|
3632
|
+
await loadChapterAnnotationCounts();
|
|
3633
|
+
await openChapterAnnotationsDialog(selection.safeStart);
|
|
3596
3634
|
} catch (error) {
|
|
3597
3635
|
toast(error.message, "error");
|
|
3598
3636
|
}
|
|
@@ -3620,7 +3658,7 @@ function chapterAnnotationCard(annotation, { showSource = false } = {}) {
|
|
|
3620
3658
|
<blockquote>${esc(annotation.quote || "空白行")}</blockquote>
|
|
3621
3659
|
<p data-annotation-content>${esc(annotation.note)}</p>
|
|
3622
3660
|
<small>${esc(annotation.actor)} · ${esc(formatDateTime(annotation.updatedAt))} · v${Number(annotation.versionNo)}</small>
|
|
3623
|
-
<footer><button type="button" data-annotation-locate>定位原文</button>${
|
|
3661
|
+
<footer><button type="button" data-annotation-locate>定位原文</button>${canManageChapterAnnotation(annotation) ? `<button type="button" data-annotation-edit>${annotation.kind === "todo" ? "编辑待办" : "编辑评论"}</button><button type="button" data-annotation-status>${annotation.status === "resolved" ? "重新打开" : (annotation.kind === "todo" ? "完成待办" : "解决评论")}</button><button class="danger-button" type="button" data-annotation-delete>${annotation.kind === "todo" ? "删除待办" : "删除评论"}</button>` : ""}</footer>
|
|
3624
3662
|
</article>`;
|
|
3625
3663
|
}
|
|
3626
3664
|
|
|
@@ -3680,9 +3718,12 @@ function renderChapterAnnotations() {
|
|
|
3680
3718
|
const host = $("#chapter-annotations-list");
|
|
3681
3719
|
host.innerHTML = chapterAnnotations.length
|
|
3682
3720
|
? chapterAnnotations.map((annotation) => chapterAnnotationCard(annotation)).join("")
|
|
3683
|
-
:
|
|
3721
|
+
: `<p class="entity-history-empty">${chapterAnnotationsLineIndex === null ? "本章还没有评论。在正文行上点击右键即可添加。" : `第 ${chapterAnnotationsLineIndex + 1} 行还没有评论或待办。`}</p>`;
|
|
3684
3722
|
bindChapterAnnotationCards(host, chapterAnnotations, {
|
|
3685
|
-
refresh:
|
|
3723
|
+
refresh: async () => {
|
|
3724
|
+
await loadChapterAnnotationCounts();
|
|
3725
|
+
await loadChapterAnnotations(chapterAnnotationsLineIndex);
|
|
3726
|
+
},
|
|
3686
3727
|
locate: (annotation) => {
|
|
3687
3728
|
$("#chapter-annotations-dialog").close();
|
|
3688
3729
|
paintChapterLineSelection(annotation.startLine - 1, annotation.endLine - 1);
|
|
@@ -3692,19 +3733,43 @@ function renderChapterAnnotations() {
|
|
|
3692
3733
|
});
|
|
3693
3734
|
}
|
|
3694
3735
|
|
|
3695
|
-
async function
|
|
3736
|
+
async function loadChapterAnnotationCounts(chapterId = state.chapter?.id) {
|
|
3737
|
+
if (!chapterId || !canReadModule("comments") && !canReadModule("todos")) {
|
|
3738
|
+
chapterAnnotationCounts = new Map();
|
|
3739
|
+
scheduleChapterLineNumbers();
|
|
3740
|
+
return;
|
|
3741
|
+
}
|
|
3742
|
+
const counts = await api(`/api/chapters/${encodeURIComponent(chapterId)}/annotation-counts`);
|
|
3743
|
+
if (String(state.chapter?.id ?? "") !== String(chapterId)) return;
|
|
3744
|
+
chapterAnnotationCounts = new Map(
|
|
3745
|
+
(Array.isArray(counts) ? counts : [])
|
|
3746
|
+
.map((item) => [Number(item.line), Number(item.count)])
|
|
3747
|
+
.filter(([line, count]) => Number.isInteger(line) && line > 0 && Number.isInteger(count) && count > 0)
|
|
3748
|
+
);
|
|
3749
|
+
scheduleChapterLineNumbers();
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
async function loadChapterAnnotations(lineIndex = chapterAnnotationsLineIndex) {
|
|
3696
3753
|
if (!state.chapter) return;
|
|
3697
|
-
|
|
3754
|
+
const chapterId = state.chapter.id;
|
|
3755
|
+
const line = Number.isInteger(lineIndex) && lineIndex >= 0 ? lineIndex + 1 : null;
|
|
3756
|
+
const query = line === null ? "" : `?line=${encodeURIComponent(line)}`;
|
|
3757
|
+
const annotations = await api(`/api/chapters/${encodeURIComponent(chapterId)}/annotations${query}`);
|
|
3758
|
+
if (String(state.chapter?.id ?? "") !== String(chapterId)) return;
|
|
3759
|
+
chapterAnnotationsLineIndex = line === null ? null : line - 1;
|
|
3760
|
+
chapterAnnotations = annotations;
|
|
3698
3761
|
renderChapterAnnotations();
|
|
3699
3762
|
}
|
|
3700
3763
|
|
|
3701
|
-
async function openChapterAnnotationsDialog() {
|
|
3764
|
+
async function openChapterAnnotationsDialog(lineIndex = null) {
|
|
3702
3765
|
if (!state.chapter) return;
|
|
3703
|
-
|
|
3766
|
+
chapterAnnotationsLineIndex = Number.isInteger(lineIndex) && lineIndex >= 0 ? lineIndex : null;
|
|
3767
|
+
const lineLabel = chapterAnnotationsLineIndex === null ? "" : `第 ${chapterAnnotationsLineIndex + 1} 行的`;
|
|
3768
|
+
$("#chapter-annotations-meta").textContent = `《${state.chapter.title}》${lineLabel}正文评论与待办`;
|
|
3704
3769
|
$("#chapter-annotations-list").innerHTML = '<p class="entity-history-empty">正在加载评论…</p>';
|
|
3705
3770
|
if (!$("#chapter-annotations-dialog").open) $("#chapter-annotations-dialog").showModal();
|
|
3706
3771
|
try {
|
|
3707
|
-
await loadChapterAnnotations();
|
|
3772
|
+
await loadChapterAnnotations(chapterAnnotationsLineIndex);
|
|
3708
3773
|
} catch (error) {
|
|
3709
3774
|
$("#chapter-annotations-dialog").close();
|
|
3710
3775
|
toast(error.message, "error");
|
|
@@ -3722,8 +3787,8 @@ function showLineCitationMenu(event, lineIndex) {
|
|
|
3722
3787
|
selectChapterLines(lineIndex, lineIndex);
|
|
3723
3788
|
}
|
|
3724
3789
|
const menu = $("#line-citation-menu");
|
|
3725
|
-
$("#add-line-annotation").classList.toggle("hidden", !
|
|
3726
|
-
$("#add-line-todo").classList.toggle("hidden", !
|
|
3790
|
+
$("#add-line-annotation").classList.toggle("hidden", !canWritePermissionModule(state.work, "comments"));
|
|
3791
|
+
$("#add-line-todo").classList.toggle("hidden", !canWritePermissionModule(state.work, "todos"));
|
|
3727
3792
|
const { start, end } = chapterLineSelection;
|
|
3728
3793
|
$("#line-citation-label").textContent = start === end ? `第 ${start + 1} 行` : `第 ${start + 1}-${end + 1} 行`;
|
|
3729
3794
|
menu.classList.remove("hidden");
|
|
@@ -3736,7 +3801,7 @@ function clearChapterLineSelection() {
|
|
|
3736
3801
|
chapterLineSelection = null;
|
|
3737
3802
|
$("#chapter-line-numbers-inner")?.querySelectorAll(".is-line-selected").forEach((row) => {
|
|
3738
3803
|
row.classList.remove("is-line-selected");
|
|
3739
|
-
row.setAttribute("aria-pressed", "false");
|
|
3804
|
+
row.querySelector(".chapter-line-number-select")?.setAttribute("aria-pressed", "false");
|
|
3740
3805
|
});
|
|
3741
3806
|
}
|
|
3742
3807
|
|
|
@@ -5860,7 +5925,7 @@ function renderMemberPermissionGrid(value) {
|
|
|
5860
5925
|
<select data-member-permission="${esc(item.id)}" aria-label="${esc(item.label)}权限">
|
|
5861
5926
|
<option value="none" ${permissions[item.id] === "none" ? "selected" : ""}>无权限</option>
|
|
5862
5927
|
<option value="read" ${permissions[item.id] === "read" ? "selected" : ""}>只读</option>
|
|
5863
|
-
<option value="write" ${permissions[item.id] === "write" ? "selected" : ""}
|
|
5928
|
+
<option value="write" ${permissions[item.id] === "write" ? "selected" : ""}>${["comments", "todos"].includes(item.id) ? "可添加" : "可编辑"}</option>
|
|
5864
5929
|
</select>
|
|
5865
5930
|
</label>`).join("");
|
|
5866
5931
|
}
|
|
@@ -6458,6 +6523,7 @@ function resetWorkScopedUiCaches() {
|
|
|
6458
6523
|
state.characters = [];
|
|
6459
6524
|
state.settings = [];
|
|
6460
6525
|
state.races = [];
|
|
6526
|
+
state.organizations = [];
|
|
6461
6527
|
characterListPage = 1;
|
|
6462
6528
|
draftTypeFilter = "all";
|
|
6463
6529
|
draftBindingFilters = [];
|
|
@@ -7174,8 +7240,12 @@ async function selectChapter(chapterId, { editMode = false } = {}) {
|
|
|
7174
7240
|
const normalizedContent = normalizeParagraphSpacing(state.chapter.content);
|
|
7175
7241
|
const spacingChanged = normalizedContent !== state.chapter.content;
|
|
7176
7242
|
$("#chapter-content").value = normalizedContent;
|
|
7243
|
+
chapterAnnotationCounts = new Map();
|
|
7177
7244
|
clearChapterLineSelection();
|
|
7178
7245
|
scheduleChapterLineNumbers();
|
|
7246
|
+
void loadChapterAnnotationCounts(state.chapter.id).catch((error) => {
|
|
7247
|
+
if (String(state.chapter?.id ?? "") === String(chapterId)) toast("正文评论数量读取失败,请稍后重试", "error");
|
|
7248
|
+
});
|
|
7179
7249
|
dismissChapterInsightToast();
|
|
7180
7250
|
updateChapterStats();
|
|
7181
7251
|
if (!canEditProse()) setSaveState("正文只读");
|
|
@@ -7711,7 +7781,7 @@ const moduleMeta = {
|
|
|
7711
7781
|
timeline: ["剧情脉络", "大事件时间轴", "候选事件经作者确认后,才进入正式时间线。", "新建事件"],
|
|
7712
7782
|
outlines: ["创作规划", "大纲/伏笔", "为每章维护目标、冲突与转折,并持续提醒尚未回收的伏笔。", "新建伏笔"],
|
|
7713
7783
|
relationships: ["跨章证据", "人物关系", "记录关系方向、阶段、置信度与原文依据。", "新建关系"],
|
|
7714
|
-
comments: ["正文协作", "
|
|
7784
|
+
comments: ["正文协作", "正文评论与待办", "集中查看并处理当前作品所有章节的评论与待办。", ""],
|
|
7715
7785
|
reviews: ["作者决策", "审核队列", "集中处理冲突、候选设定、低置信度关系和时间问题。", "新增审核项"],
|
|
7716
7786
|
tasks: ["AI 深度分析", "AI 分析中心", "对全书或指定章节运行人物关系、世界观、设定、事件与一致性分析。", "开始 AI 分析"],
|
|
7717
7787
|
"ai-settings": ["书籍提示词", "本书 AI 设置", "本书系统提示词会追加在内置提示词和平台全局提示词之后;任务默认模型只作用于当前作品。", "保存设置"]
|
|
@@ -9281,8 +9351,8 @@ async function renderWorkChapterComments(page = moduleListPages.comments) {
|
|
|
9281
9351
|
moduleListPages.comments = pageResult.page;
|
|
9282
9352
|
mountModuleCount(total);
|
|
9283
9353
|
$("#module-content").innerHTML = result.items.length
|
|
9284
|
-
? `<div class="chapter-comment-module-list">${result.items.map((annotation) => chapterAnnotationCard(annotation, { showSource: true })).join("")}</div>${renderModulePagination(pageResult, "comments", "
|
|
9285
|
-
: emptyModule("
|
|
9354
|
+
? `<div class="chapter-comment-module-list">${result.items.map((annotation) => chapterAnnotationCard(annotation, { showSource: true })).join("")}</div>${renderModulePagination(pageResult, "comments", "正文评论与待办列表")}`
|
|
9355
|
+
: emptyModule("还没有正文评论或待办", "在任一正文行上点击右键,即可添加评论或待办。");
|
|
9286
9356
|
bindModulePagination("comments", renderWorkChapterComments);
|
|
9287
9357
|
bindChapterAnnotationCards($("#module-content"), result.items, {
|
|
9288
9358
|
refresh: () => renderWorkChapterComments(pageResult.page),
|
|
@@ -11491,13 +11561,17 @@ async function loadAiReferences() {
|
|
|
11491
11561
|
const workId = state.work?.id;
|
|
11492
11562
|
if (!workId) return;
|
|
11493
11563
|
const generation = workScopedUiGeneration;
|
|
11494
|
-
const [characters, settings] = await Promise.all([
|
|
11564
|
+
const [characters, settings, races, organizations] = await Promise.all([
|
|
11495
11565
|
canReadModule("characters") ? apiAllPages(`/api/works/${workId}/characters`) : Promise.resolve([]),
|
|
11496
|
-
canReadModule("settings") ? api(`/api/works/${workId}/settings/context`) : Promise.resolve([])
|
|
11566
|
+
canReadModule("settings") ? api(`/api/works/${workId}/settings/context`) : Promise.resolve([]),
|
|
11567
|
+
canReadModule("races") ? api(`/api/works/${workId}/races`) : Promise.resolve([]),
|
|
11568
|
+
canReadModule("organizations") ? apiAllPages(`/api/works/${workId}/organizations`) : Promise.resolve([])
|
|
11497
11569
|
]);
|
|
11498
11570
|
if (state.work?.id !== workId || generation !== workScopedUiGeneration) return;
|
|
11499
11571
|
state.characters = characters;
|
|
11500
11572
|
state.settings = settings;
|
|
11573
|
+
state.races = races;
|
|
11574
|
+
state.organizations = organizations;
|
|
11501
11575
|
renderAiRoleplayCharacterSelect();
|
|
11502
11576
|
loadedAiReferencesWorkId = workId;
|
|
11503
11577
|
}
|
|
@@ -14629,6 +14703,7 @@ async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
|
14629
14703
|
try {
|
|
14630
14704
|
try {
|
|
14631
14705
|
await ensureAiModelsLoaded();
|
|
14706
|
+
await ensureAiReferencesLoaded();
|
|
14632
14707
|
} catch (error) {
|
|
14633
14708
|
if (isAiRequestCancellation(error, requestHolder.snapshot) || !aiRequestTargetsCurrentState(requestHolder.snapshot)) throw error;
|
|
14634
14709
|
setAiChatTabStatus(tab, "error");
|
|
@@ -14876,6 +14951,7 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
14876
14951
|
}
|
|
14877
14952
|
});
|
|
14878
14953
|
let streamedText = "";
|
|
14954
|
+
let streamedPendingText = "";
|
|
14879
14955
|
let generatedMetadata = {};
|
|
14880
14956
|
let toolCalls = [];
|
|
14881
14957
|
let processSteps = [];
|
|
@@ -14886,7 +14962,6 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
14886
14962
|
let contextAction = "ready";
|
|
14887
14963
|
let warningOnly = false;
|
|
14888
14964
|
let streamContextCompacted = false;
|
|
14889
|
-
let finalAnswerStarted = false;
|
|
14890
14965
|
const processStartedAt = Date.now();
|
|
14891
14966
|
const elapsedProcessTime = () => Math.max(0, Date.now() - processStartedAt);
|
|
14892
14967
|
const processStepTypewriters = new Map();
|
|
@@ -14904,7 +14979,7 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
14904
14979
|
onRender: (text) => {
|
|
14905
14980
|
if (!aiRequestTargetsCurrentState(requestHolder.snapshot)) return;
|
|
14906
14981
|
processStepVisibleContents.set(step, text);
|
|
14907
|
-
renderStreamingProcessSteps(
|
|
14982
|
+
renderStreamingProcessSteps(false);
|
|
14908
14983
|
scrollAiFeedToBottom(feed);
|
|
14909
14984
|
}
|
|
14910
14985
|
});
|
|
@@ -14977,18 +15052,21 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
14977
15052
|
}
|
|
14978
15053
|
} else if (eventName === "delta") {
|
|
14979
15054
|
mountAssistantMessage();
|
|
14980
|
-
const firstFinalDelta = streamedText.length === 0;
|
|
14981
15055
|
const delta = typeof payload.delta === "string" ? payload.delta : "";
|
|
14982
15056
|
streamedText += delta;
|
|
14983
|
-
|
|
15057
|
+
streamedPendingText += delta;
|
|
14984
15058
|
typewriter.append(delta);
|
|
14985
|
-
if (firstFinalDelta && processSteps.length) renderStreamingProcessSteps(true, elapsedProcessTime());
|
|
14986
15059
|
meta.textContent = "正在生成回复……";
|
|
14987
15060
|
} else if (eventName === "process_step") {
|
|
14988
15061
|
mountAssistantMessage();
|
|
14989
15062
|
const step = { ...payload };
|
|
14990
15063
|
const append = step.append === true;
|
|
14991
15064
|
delete step.append;
|
|
15065
|
+
if (step.type === "intermediate" && typeof step.content === "string" && streamedPendingText.endsWith(step.content)) {
|
|
15066
|
+
streamedPendingText = streamedPendingText.slice(0, -step.content.length);
|
|
15067
|
+
streamedText = streamedText.slice(0, -step.content.length);
|
|
15068
|
+
typewriter.replace(streamedText);
|
|
15069
|
+
}
|
|
14992
15070
|
const existing = append ? processSteps.find((item) => item.id === step.id && item.type === step.type) : null;
|
|
14993
15071
|
if (existing && typeof step.content === "string") existing.content += step.content;
|
|
14994
15072
|
else processSteps.push(step);
|
|
@@ -14996,7 +15074,7 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
14996
15074
|
if (typeof step.content === "string" && step.content.length > 0 && step.type === "thinking") {
|
|
14997
15075
|
processStepTypewriter(targetStep).append(step.content);
|
|
14998
15076
|
}
|
|
14999
|
-
renderStreamingProcessSteps(
|
|
15077
|
+
renderStreamingProcessSteps(false, elapsedProcessTime());
|
|
15000
15078
|
meta.textContent = step.type === "thinking"
|
|
15001
15079
|
? `正在思考 · 第 ${Number(step.round) || 1} 轮`
|
|
15002
15080
|
: step.type === "context_compaction"
|
|
@@ -15011,7 +15089,7 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
15011
15089
|
if (toolCall.status === "failed") setAiChatTabStatus(tab, "error");
|
|
15012
15090
|
toolCalls.push(toolCall);
|
|
15013
15091
|
processSteps.push(aiToolProcessStep(toolCall, round));
|
|
15014
|
-
renderStreamingProcessSteps(
|
|
15092
|
+
renderStreamingProcessSteps(false, elapsedProcessTime());
|
|
15015
15093
|
meta.textContent = `已调用 ${toolCalls.length} 个工具,正在等待模型处理结果`;
|
|
15016
15094
|
scrollAiFeedToBottom(feed);
|
|
15017
15095
|
} else if (eventName === "context_compacted") {
|
|
@@ -15135,20 +15213,27 @@ function appendMessage(role, text, citations = [], createdAt = null, metadata =
|
|
|
15135
15213
|
failureBadge.setAttribute("aria-label", `消息状态:${isInterrupted ? aiStreamInterruptionLabel(interruptionCode) : "失败"}`);
|
|
15136
15214
|
heading.firstElementChild?.append(failureBadge);
|
|
15137
15215
|
}
|
|
15138
|
-
const
|
|
15139
|
-
?
|
|
15216
|
+
const mentionGroups = role === "user"
|
|
15217
|
+
? [
|
|
15218
|
+
["角色", metadata?.mentionCharacterIds, state.characters],
|
|
15219
|
+
["种族", metadata?.mentionRaceIds, state.races],
|
|
15220
|
+
["组织", metadata?.mentionOrganizationIds, state.organizations]
|
|
15221
|
+
]
|
|
15222
|
+
.flatMap(([kind, ids, items]) => userMessageMentionNames(ids, items).map((name) => ({ kind, name })))
|
|
15140
15223
|
: [];
|
|
15141
|
-
if (
|
|
15224
|
+
if (mentionGroups.length) {
|
|
15142
15225
|
const references = document.createElement("div");
|
|
15143
15226
|
references.className = "user-message-mentions";
|
|
15144
15227
|
const label = document.createElement("span");
|
|
15145
15228
|
label.className = "user-message-mentions-label";
|
|
15146
|
-
label.textContent = "
|
|
15229
|
+
label.textContent = "引用";
|
|
15147
15230
|
references.append(label);
|
|
15148
|
-
for (const name of
|
|
15231
|
+
for (const { kind, name } of mentionGroups) {
|
|
15149
15232
|
const reference = document.createElement("span");
|
|
15150
15233
|
reference.className = "user-message-mention";
|
|
15151
15234
|
reference.textContent = name;
|
|
15235
|
+
reference.title = `${kind}:${name}`;
|
|
15236
|
+
reference.setAttribute("aria-label", `${kind}:${name}`);
|
|
15152
15237
|
references.append(reference);
|
|
15153
15238
|
}
|
|
15154
15239
|
message.append(references);
|
|
@@ -15174,6 +15259,7 @@ function appendMessage(role, text, citations = [], createdAt = null, metadata =
|
|
|
15174
15259
|
if (role === "assistant") {
|
|
15175
15260
|
renderAiProcessSteps(message, processSteps, true, processDurationMs);
|
|
15176
15261
|
}
|
|
15262
|
+
if (role === "user") attachUserCopyAction(message, text);
|
|
15177
15263
|
if (role === "assistant" && !text.startsWith("调用失败:")) {
|
|
15178
15264
|
const selectedModelId = tab?.modelId ?? tab?.selectedModelId ?? $("#ai-model").value;
|
|
15179
15265
|
const selectedModel = state.models.find((model) => model.id === selectedModelId) ?? state.models[0];
|
|
@@ -16648,6 +16734,7 @@ $("#chapter-content").addEventListener("contextmenu", (event) => {
|
|
|
16648
16734
|
showLineCitationMenu(event, lineIndexAtPointer(event.clientY));
|
|
16649
16735
|
});
|
|
16650
16736
|
$("#chapter-line-numbers-inner").addEventListener("pointerdown", (event) => {
|
|
16737
|
+
if (event.target.closest(".chapter-line-annotation-count")) return;
|
|
16651
16738
|
const row = event.target.closest(".chapter-line-number");
|
|
16652
16739
|
if (!row || event.button !== 0) return;
|
|
16653
16740
|
event.preventDefault();
|
|
@@ -16670,7 +16757,23 @@ const finishChapterLineDrag = (event) => {
|
|
|
16670
16757
|
};
|
|
16671
16758
|
$("#chapter-line-numbers-inner").addEventListener("pointerup", finishChapterLineDrag);
|
|
16672
16759
|
$("#chapter-line-numbers-inner").addEventListener("pointercancel", finishChapterLineDrag);
|
|
16760
|
+
$("#chapter-line-numbers-inner").addEventListener("click", (event) => {
|
|
16761
|
+
const bubble = event.target.closest(".chapter-line-annotation-count");
|
|
16762
|
+
if (bubble) {
|
|
16763
|
+
event.preventDefault();
|
|
16764
|
+
event.stopPropagation();
|
|
16765
|
+
openChapterAnnotationsDialog(Number(bubble.dataset.lineIndex)).catch((error) => toast(error.message, "error"));
|
|
16766
|
+
return;
|
|
16767
|
+
}
|
|
16768
|
+
const lineSelect = event.target.closest(".chapter-line-number-select");
|
|
16769
|
+
if (!lineSelect) return;
|
|
16770
|
+
const lineIndex = Number(lineSelect.closest(".chapter-line-number")?.dataset.lineIndex);
|
|
16771
|
+
if (!Number.isInteger(lineIndex)) return;
|
|
16772
|
+
paintChapterLineSelection(lineIndex, lineIndex);
|
|
16773
|
+
selectChapterLines(lineIndex, lineIndex);
|
|
16774
|
+
});
|
|
16673
16775
|
$("#chapter-line-numbers-inner").addEventListener("contextmenu", (event) => {
|
|
16776
|
+
if (event.target.closest(".chapter-line-annotation-count")) return;
|
|
16674
16777
|
const row = event.target.closest(".chapter-line-number");
|
|
16675
16778
|
if (row) showLineCitationMenu(event, Number(row.dataset.lineIndex));
|
|
16676
16779
|
});
|
|
@@ -16680,7 +16783,10 @@ $("#add-line-todo").addEventListener("click", () => createSelectedLineAnnotation
|
|
|
16680
16783
|
$("#chapter-annotations-button").addEventListener("click", () => openChapterAnnotationsDialog().catch((error) => toast(error.message, "error")));
|
|
16681
16784
|
$("#chapter-annotations-close").addEventListener("click", () => $("#chapter-annotations-dialog").close());
|
|
16682
16785
|
$("#chapter-annotations-done").addEventListener("click", () => $("#chapter-annotations-dialog").close());
|
|
16683
|
-
$("#chapter-annotations-refresh").addEventListener("click", () =>
|
|
16786
|
+
$("#chapter-annotations-refresh").addEventListener("click", () => Promise.all([
|
|
16787
|
+
loadChapterAnnotationCounts(),
|
|
16788
|
+
loadChapterAnnotations(chapterAnnotationsLineIndex)
|
|
16789
|
+
]).catch((error) => toast(error.message, "error")));
|
|
16684
16790
|
$("#left-panel-toggle").addEventListener("click", () => {
|
|
16685
16791
|
panelLayout.leftCollapsed = !panelLayout.leftCollapsed;
|
|
16686
16792
|
applyPanelLayout(true);
|
|
@@ -16718,6 +16824,8 @@ $("#ai-assistant-entry").addEventListener("click", () => {
|
|
|
16718
16824
|
if (isMobileViewport()) {
|
|
16719
16825
|
panelLayout.leftCollapsed = true;
|
|
16720
16826
|
applyPanelLayout(true);
|
|
16827
|
+
setAiConversationWorkspaceVisible(true);
|
|
16828
|
+
return;
|
|
16721
16829
|
}
|
|
16722
16830
|
setAiConversationWorkspaceVisible(true);
|
|
16723
16831
|
});
|
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">
|
|
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">
|
|
14
14
|
</head>
|
|
15
15
|
<body class="auth-pending">
|
|
16
16
|
<section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
|
|
@@ -133,8 +133,8 @@
|
|
|
133
133
|
<button type="button" data-module="relationships"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><path d="m8.59 13.51 6.83 3.98"/><path d="m15.41 6.51-6.82 3.98"/></svg>关系</button>
|
|
134
134
|
<button type="button" data-module="races"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="4" r="2"/><circle cx="18" cy="8" r="2"/><circle cx="20" cy="16" r="2"/><path d="M9 10a5 5 0 0 1 5 5v3.5a3.5 3.5 0 0 1-6.84 1.045Q6.52 17.48 4.46 16.84A3.5 3.5 0 0 1 5.5 10Z"/></svg>种族</button>
|
|
135
135
|
<button class="ai-analysis-entry" type="button" data-module="tasks"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/><path d="M4 17v2"/><path d="M5 18H3"/></svg>AI 分析</button>
|
|
136
|
-
<button id="ai-assistant-entry" class="ai-assistant-entry" type="button" aria-controls="ai-panel" aria-expanded="false"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 5.5A2.5 2.5 0 0 1 6.5 3h11A2.5 2.5 0 0 1 20 5.5v8a2.5 2.5 0 0 1-2.5 2.5H11l-4.5 4v-4h0A2.5 2.5 0 0 1 4 13.5z"/><path d="M8 8h8M8 11h5"/></svg>创作助手</button>
|
|
137
136
|
<button id="module-more-button" type="button" aria-expanded="false"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg><span class="nav-label">更多</span></button>
|
|
137
|
+
<button id="ai-assistant-entry" class="module-nav-secondary hidden ai-assistant-entry" type="button" aria-controls="ai-panel" aria-expanded="false"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 5.5A2.5 2.5 0 0 1 6.5 3h11A2.5 2.5 0 0 1 20 5.5v8a2.5 2.5 0 0 1-2.5 2.5H11l-4.5 4v-4h0A2.5 2.5 0 0 1 4 13.5z"/><path d="M8 8h8M8 11h5"/></svg>创作助手</button>
|
|
138
138
|
<button id="reader-open-button" class="module-nav-secondary hidden" type="button" aria-label="打开沉浸式阅读预览" title="阅读预览"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 5.5A2.5 2.5 0 0 1 5.5 3H11v16H5.5A2.5 2.5 0 0 0 3 21.5Z"/><path d="M21 5.5A2.5 2.5 0 0 0 18.5 3H13v16h5.5a2.5 2.5 0 0 1 2.5 2.5Z"/></svg>阅读预览</button>
|
|
139
139
|
<button class="module-nav-secondary hidden" type="button" data-module="comments"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M21 15a4 4 0 0 1-4 4H8l-5 3V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4Z"/><path d="M8 9h8M8 13h5"/></svg><span class="nav-label">正文评论</span></button>
|
|
140
140
|
<button class="module-nav-secondary hidden" type="button" data-module="outlines"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m3 17 2 2 4-4"/><path d="m3 7 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/></svg><span class="nav-label">大纲/伏笔</span></button>
|
|
@@ -1106,7 +1106,7 @@
|
|
|
1106
1106
|
<button type="button" data-permission-preset="write">全部可编辑</button>
|
|
1107
1107
|
</div>
|
|
1108
1108
|
<div id="member-permission-grid" class="member-permission-grid"></div>
|
|
1109
|
-
<small class="member-permission-help"
|
|
1109
|
+
<small class="member-permission-help">正文评论和正文待办的“可添加”只控制新增;删除、内容更新和状态更新需要正文可编辑权限。未授权模块不会显示,也不能通过 API 读取。</small>
|
|
1110
1110
|
<button id="member-permission-submit" class="primary-button" type="submit">保存成员权限</button>
|
|
1111
1111
|
</fieldset>
|
|
1112
1112
|
</form>
|
|
@@ -1205,6 +1205,6 @@
|
|
|
1205
1205
|
<div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
|
|
1206
1206
|
<script id="vditorIconScript" src="/vendor/vditor/dist/js/icons/ant.js?v=3.11.2"></script>
|
|
1207
1207
|
<script src="/vendor/vditor/dist/index.min.js?v=3.11.2"></script>
|
|
1208
|
-
<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=calculate-time-tool-v1&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-v1&feature=volume-detail-icon-v1&feature=reader-manual-chapter-navigation-v1"></script>
|
|
1208
|
+
<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=calculate-time-tool-v1&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-v1&feature=volume-detail-icon-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"></script>
|
|
1209
1209
|
</body>
|
|
1210
1210
|
</html>
|
|
@@ -122,6 +122,18 @@ export function createStreamTypewriter({
|
|
|
122
122
|
pendingCharacters.push(...characters);
|
|
123
123
|
schedule();
|
|
124
124
|
},
|
|
125
|
+
replace(value) {
|
|
126
|
+
if (scheduledFrame !== null) {
|
|
127
|
+
cancelFrame(scheduledFrame);
|
|
128
|
+
scheduledFrame = null;
|
|
129
|
+
}
|
|
130
|
+
visibleCharacters.splice(0, visibleCharacters.length, ...Array.from(String(value ?? "")));
|
|
131
|
+
pendingCharacters.splice(0);
|
|
132
|
+
finishing = false;
|
|
133
|
+
render();
|
|
134
|
+
resolveIdle();
|
|
135
|
+
return snapshot();
|
|
136
|
+
},
|
|
125
137
|
finish() {
|
|
126
138
|
if (!pendingCharacters.length && scheduledFrame === null) return Promise.resolve(snapshot());
|
|
127
139
|
finishing = true;
|