@musnows/scriverse 0.7.5 → 0.7.7
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 +88 -13
- package/dist/ai.js.map +1 -1
- package/dist/app.js +44 -45
- package/dist/app.js.map +1 -1
- package/dist/attachment-storage.js +4 -0
- package/dist/attachment-storage.js.map +1 -1
- package/dist/epub-export.js +42 -38
- package/dist/epub-export.js.map +1 -1
- package/dist/public/app.js +150 -50
- package/dist/public/index.html +6 -6
- package/dist/public/outline-board.d.ts +16 -10
- package/dist/public/outline-board.js +11 -105
- package/dist/public/styles.css +2 -2
- package/dist/store.js +239 -63
- package/dist/store.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/zip-stream.js +149 -0
- package/dist/zip-stream.js.map +1 -0
- package/package.json +1 -1
package/dist/public/app.js
CHANGED
|
@@ -84,9 +84,9 @@ import {
|
|
|
84
84
|
} from "/timeline-view.js?v=20260801-timeline-sort-actions-v1";
|
|
85
85
|
import {
|
|
86
86
|
normalizeOutlineBoardState,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
} from "/outline-board.js?v=
|
|
87
|
+
outlineBoardRequestPath,
|
|
88
|
+
outlineBoardUnresolvedCount
|
|
89
|
+
} from "/outline-board.js?v=20260813-outline-board-page-v1";
|
|
90
90
|
import { backgroundTaskActivityCount, backgroundTaskPollDelay, collectBackgroundTaskTransitions } from "/background-task-center.js?v=20260810-analysis-task-failed-v1";
|
|
91
91
|
import { createModuleRequestCache } from "/module-request-cache.js?v=20260730-module-request-cache-v1";
|
|
92
92
|
import { systemStatusPresentation } from "/system-status.js?v=20260801-system-health-v1";
|
|
@@ -1228,6 +1228,7 @@ let settingFiltersPanelOpen = false;
|
|
|
1228
1228
|
const outlineBoardFilters = normalizeOutlineBoardState();
|
|
1229
1229
|
let outlineBoardFiltersPanelOpen = false;
|
|
1230
1230
|
let outlineBoardRenderRequestId = 0;
|
|
1231
|
+
let outlineBoardSearchTimer = null;
|
|
1231
1232
|
const relationshipFilters = { fromCharacterIds: [], toCharacterIds: [] };
|
|
1232
1233
|
let relationshipFiltersPanelOpen = false;
|
|
1233
1234
|
|
|
@@ -1808,7 +1809,7 @@ function aiAssistantLabel(suffix = "") {
|
|
|
1808
1809
|
return suffix ? `${name} · ${suffix}` : name;
|
|
1809
1810
|
}
|
|
1810
1811
|
|
|
1811
|
-
function createAiContextCompactionDivider({ kind = "conversation", ariaLabel = "
|
|
1812
|
+
function createAiContextCompactionDivider({ kind = "conversation", ariaLabel = "当前上下文已压缩", title = "" } = {}) {
|
|
1812
1813
|
const divider = document.createElement("div");
|
|
1813
1814
|
divider.className = "ai-context-compaction-divider";
|
|
1814
1815
|
divider.dataset.contextCompaction = kind;
|
|
@@ -1816,7 +1817,7 @@ function createAiContextCompactionDivider({ kind = "conversation", ariaLabel = "
|
|
|
1816
1817
|
divider.setAttribute("role", "separator");
|
|
1817
1818
|
divider.setAttribute("aria-label", ariaLabel);
|
|
1818
1819
|
if (title) divider.title = title;
|
|
1819
|
-
divider.innerHTML = "<span
|
|
1820
|
+
divider.innerHTML = "<span>—— 当前上下文已压缩 ——</span>";
|
|
1820
1821
|
return divider;
|
|
1821
1822
|
}
|
|
1822
1823
|
|
|
@@ -3596,8 +3597,7 @@ async function refreshAuthCaptcha(target = "login") {
|
|
|
3596
3597
|
}
|
|
3597
3598
|
|
|
3598
3599
|
function clearAuthenticationOverlays() {
|
|
3599
|
-
|
|
3600
|
-
toastRegion.replaceChildren();
|
|
3600
|
+
clearToastRegion();
|
|
3601
3601
|
document.querySelectorAll("[popover]").forEach((popover) => {
|
|
3602
3602
|
if (typeof popover.hidePopover === "function" && popover.matches(":popover-open")) popover.hidePopover();
|
|
3603
3603
|
});
|
|
@@ -3739,16 +3739,49 @@ function raiseToastRegion() {
|
|
|
3739
3739
|
region.showPopover();
|
|
3740
3740
|
}
|
|
3741
3741
|
|
|
3742
|
-
|
|
3743
|
-
|
|
3742
|
+
const TOAST_DURATION_MS = 15_000;
|
|
3743
|
+
const toastTimers = new Map();
|
|
3744
|
+
|
|
3745
|
+
function hideToastRegionIfEmpty() {
|
|
3744
3746
|
const region = $("#toast-region");
|
|
3745
|
-
region.querySelector(".chapter-insight-toast")?.remove();
|
|
3746
|
-
$("#insight-button").setAttribute("aria-expanded", "false");
|
|
3747
3747
|
if (!region.childElementCount && typeof region.hidePopover === "function" && region.matches(":popover-open")) {
|
|
3748
3748
|
region.hidePopover();
|
|
3749
3749
|
}
|
|
3750
3750
|
}
|
|
3751
3751
|
|
|
3752
|
+
function removeToastElement(element) {
|
|
3753
|
+
const timer = toastTimers.get(element);
|
|
3754
|
+
if (timer !== undefined) {
|
|
3755
|
+
window.clearTimeout(timer);
|
|
3756
|
+
toastTimers.delete(element);
|
|
3757
|
+
}
|
|
3758
|
+
element.remove();
|
|
3759
|
+
hideToastRegionIfEmpty();
|
|
3760
|
+
}
|
|
3761
|
+
|
|
3762
|
+
function clearToastRegion() {
|
|
3763
|
+
toastTimers.forEach((timer) => window.clearTimeout(timer));
|
|
3764
|
+
toastTimers.clear();
|
|
3765
|
+
$("#toast-region").replaceChildren();
|
|
3766
|
+
hideToastRegionIfEmpty();
|
|
3767
|
+
}
|
|
3768
|
+
|
|
3769
|
+
function clearTransientToasts() {
|
|
3770
|
+
const region = $("#toast-region");
|
|
3771
|
+
[...region.children]
|
|
3772
|
+
.filter((element) => !element.classList.contains("toast-confirmation") && !element.classList.contains("chapter-insight-toast"))
|
|
3773
|
+
.forEach(removeToastElement);
|
|
3774
|
+
}
|
|
3775
|
+
|
|
3776
|
+
function dismissChapterInsightToast() {
|
|
3777
|
+
chapterInsightRequestId += 1;
|
|
3778
|
+
const region = $("#toast-region");
|
|
3779
|
+
const element = region.querySelector(".chapter-insight-toast");
|
|
3780
|
+
if (element) removeToastElement(element);
|
|
3781
|
+
$("#insight-button").setAttribute("aria-expanded", "false");
|
|
3782
|
+
hideToastRegionIfEmpty();
|
|
3783
|
+
}
|
|
3784
|
+
|
|
3752
3785
|
function toast(message, type = "info") {
|
|
3753
3786
|
if (systemRestartDetected || (!state.user && document.documentElement.classList.contains("login-route"))) return;
|
|
3754
3787
|
const region = $("#toast-region");
|
|
@@ -3759,12 +3792,7 @@ function toast(message, type = "info") {
|
|
|
3759
3792
|
element.textContent = message;
|
|
3760
3793
|
region.append(element);
|
|
3761
3794
|
raiseToastRegion();
|
|
3762
|
-
setTimeout(() =>
|
|
3763
|
-
element.remove();
|
|
3764
|
-
if (!region.childElementCount && typeof region.hidePopover === "function" && region.matches(":popover-open")) {
|
|
3765
|
-
region.hidePopover();
|
|
3766
|
-
}
|
|
3767
|
-
}, 3600);
|
|
3795
|
+
toastTimers.set(element, window.setTimeout(() => removeToastElement(element), TOAST_DURATION_MS));
|
|
3768
3796
|
}
|
|
3769
3797
|
|
|
3770
3798
|
async function runEntityEditorSave({ busyTarget, button, prepare, save }) {
|
|
@@ -4262,6 +4290,7 @@ async function initializePage() {
|
|
|
4262
4290
|
function showShelf() {
|
|
4263
4291
|
stopBackgroundTaskCenter();
|
|
4264
4292
|
dismissChapterInsightToast();
|
|
4293
|
+
clearTransientToasts();
|
|
4265
4294
|
state.dirty = false;
|
|
4266
4295
|
settingsReturnContext = null;
|
|
4267
4296
|
updateDocumentTitle();
|
|
@@ -4562,6 +4591,7 @@ async function showWorkAudit() {
|
|
|
4562
4591
|
workAuditRecords = [];
|
|
4563
4592
|
workAuditNextPage = null;
|
|
4564
4593
|
dismissChapterInsightToast();
|
|
4594
|
+
clearTransientToasts();
|
|
4565
4595
|
updateDocumentTitle(state.work);
|
|
4566
4596
|
$("#app").classList.add("shelf-mode");
|
|
4567
4597
|
$("#shelf-view").classList.add("hidden");
|
|
@@ -5484,6 +5514,7 @@ async function showSettingsHub() {
|
|
|
5484
5514
|
state.dirty = false;
|
|
5485
5515
|
}
|
|
5486
5516
|
dismissChapterInsightToast();
|
|
5517
|
+
clearTransientToasts();
|
|
5487
5518
|
updateDocumentTitle(state.work);
|
|
5488
5519
|
$("#app").classList.add("shelf-mode");
|
|
5489
5520
|
$("#shelf-view").classList.add("hidden");
|
|
@@ -5531,6 +5562,7 @@ async function showPlatformAi() {
|
|
|
5531
5562
|
if (state.dirty && !(await confirmDiscardChanges("当前章节有未保存修改,进入平台 AI 管理将放弃本地修改。是否继续?"))) return false;
|
|
5532
5563
|
state.dirty = false;
|
|
5533
5564
|
dismissChapterInsightToast();
|
|
5565
|
+
clearTransientToasts();
|
|
5534
5566
|
updateDocumentTitle();
|
|
5535
5567
|
$("#app").classList.add("shelf-mode");
|
|
5536
5568
|
$("#shelf-view").classList.add("hidden");
|
|
@@ -5552,6 +5584,8 @@ async function showPlatformAi() {
|
|
|
5552
5584
|
async function showPlatformUsage() {
|
|
5553
5585
|
if (state.dirty && !(await confirmDiscardChanges("当前章节有未保存修改,进入 Token 用量面板将放弃本地修改。是否继续?"))) return false;
|
|
5554
5586
|
state.dirty = false;
|
|
5587
|
+
dismissChapterInsightToast();
|
|
5588
|
+
clearTransientToasts();
|
|
5555
5589
|
updateDocumentTitle();
|
|
5556
5590
|
$("#app").classList.add("shelf-mode");
|
|
5557
5591
|
$("#shelf-view").classList.add("hidden");
|
|
@@ -5627,6 +5661,8 @@ function resetWorkScopedUiCaches() {
|
|
|
5627
5661
|
Object.assign(outlineBoardFilters, normalizeOutlineBoardState());
|
|
5628
5662
|
outlineBoardFiltersPanelOpen = false;
|
|
5629
5663
|
outlineBoardRenderRequestId += 1;
|
|
5664
|
+
clearTimeout(outlineBoardSearchTimer);
|
|
5665
|
+
outlineBoardSearchTimer = null;
|
|
5630
5666
|
chapterSelectionRequestGeneration += 1;
|
|
5631
5667
|
Object.keys(moduleListPages).forEach((key) => { moduleListPages[key] = 1; });
|
|
5632
5668
|
relationshipFilters.fromCharacterIds = [];
|
|
@@ -6344,6 +6380,7 @@ async function selectChapter(chapterId, { editMode = false } = {}) {
|
|
|
6344
6380
|
clearChapterLineSelection();
|
|
6345
6381
|
scheduleChapterLineNumbers();
|
|
6346
6382
|
dismissChapterInsightToast();
|
|
6383
|
+
clearTransientToasts();
|
|
6347
6384
|
updateChapterStats();
|
|
6348
6385
|
if (!canEditProse()) setSaveState("正文只读");
|
|
6349
6386
|
else if (chapterEditorReadOnly) setSaveState("阅读模式");
|
|
@@ -6862,6 +6899,7 @@ function showWelcome(hasWork = false) {
|
|
|
6862
6899
|
chapterSelectionRequestId += 1;
|
|
6863
6900
|
clearChapterForeshadowReminders({ invalidateRequest: true });
|
|
6864
6901
|
dismissChapterInsightToast();
|
|
6902
|
+
clearTransientToasts();
|
|
6865
6903
|
$("#editor-view").classList.add("hidden");
|
|
6866
6904
|
$("#module-view").classList.add("hidden");
|
|
6867
6905
|
$("#welcome-view").classList.remove("hidden");
|
|
@@ -6925,6 +6963,7 @@ async function showModule(module) {
|
|
|
6925
6963
|
}
|
|
6926
6964
|
showSystemStatus();
|
|
6927
6965
|
dismissChapterInsightToast();
|
|
6966
|
+
clearTransientToasts();
|
|
6928
6967
|
$("#welcome-view").classList.add("hidden");
|
|
6929
6968
|
$("#editor-view").classList.add("hidden");
|
|
6930
6969
|
$("#module-view").classList.remove("hidden");
|
|
@@ -8198,16 +8237,17 @@ function bindOutlineBoardCards(board, workId) {
|
|
|
8198
8237
|
}));
|
|
8199
8238
|
}
|
|
8200
8239
|
|
|
8201
|
-
async function renderOutlines(foreshadowPage = moduleListPages.foreshadows) {
|
|
8240
|
+
async function renderOutlines(foreshadowPage = moduleListPages.foreshadows, boardPage = moduleListPages.outlinePlans) {
|
|
8202
8241
|
const workId = state.work.id;
|
|
8203
8242
|
const generation = workScopedUiGeneration;
|
|
8204
8243
|
const requestId = ++outlineBoardRenderRequestId;
|
|
8205
8244
|
const currentChapterId = state.chapter?.id;
|
|
8245
|
+
const boardPageSize = pageSizeFor("outlines");
|
|
8206
8246
|
let board;
|
|
8207
8247
|
let foreshadows;
|
|
8208
8248
|
try {
|
|
8209
8249
|
[board, foreshadows] = await Promise.all([
|
|
8210
|
-
moduleApi("outlines",
|
|
8250
|
+
moduleApi("outlines", outlineBoardRequestPath(workId, outlineBoardFilters, boardPage, boardPageSize)),
|
|
8211
8251
|
moduleApiAllPages("outlines", `/api/works/${encodeURIComponent(workId)}/foreshadows?status=all${currentChapterId ? `¤tChapterId=${encodeURIComponent(currentChapterId)}` : ""}`)
|
|
8212
8252
|
]);
|
|
8213
8253
|
} catch (error) {
|
|
@@ -8216,9 +8256,12 @@ async function renderOutlines(foreshadowPage = moduleListPages.foreshadows) {
|
|
|
8216
8256
|
}
|
|
8217
8257
|
if (state.work?.id !== workId || generation !== workScopedUiGeneration || requestId !== outlineBoardRenderRequestId || state.module !== "outlines") return;
|
|
8218
8258
|
|
|
8219
|
-
if (outlineBoardFilters.volumeId && !board.
|
|
8259
|
+
if (outlineBoardFilters.volumeId && !board.volumeOptions.some((volume) => String(volume.id) === outlineBoardFilters.volumeId)) {
|
|
8220
8260
|
outlineBoardFilters.volumeId = "";
|
|
8261
|
+
board = await moduleApi("outlines", outlineBoardRequestPath(workId, outlineBoardFilters, 1, boardPageSize));
|
|
8262
|
+
if (state.work?.id !== workId || generation !== workScopedUiGeneration || requestId !== outlineBoardRenderRequestId || state.module !== "outlines") return;
|
|
8221
8263
|
}
|
|
8264
|
+
moduleListPages.outlinePlans = board.page;
|
|
8222
8265
|
const foreshadowPageResult = paginateModuleItems(foreshadows, foreshadowPage, "outlines");
|
|
8223
8266
|
moduleListPages.foreshadows = foreshadowPageResult.page;
|
|
8224
8267
|
const layout = readModuleLayout();
|
|
@@ -8254,9 +8297,9 @@ async function renderOutlines(foreshadowPage = moduleListPages.foreshadows) {
|
|
|
8254
8297
|
|
|
8255
8298
|
const filterState = normalizeOutlineBoardState(outlineBoardFilters);
|
|
8256
8299
|
Object.assign(outlineBoardFilters, filterState);
|
|
8257
|
-
const volumeOptions = board.
|
|
8300
|
+
const volumeOptions = board.volumeOptions.map((volume) => `<option value="${esc(volume.id)}" ${filterState.volumeId === String(volume.id) ? "selected" : ""}>${esc(volume.title)}</option>`).join("");
|
|
8258
8301
|
const filterToolbar = `<section id="outline-board-filter-panel" class="character-filter-toolbar outline-board-filter-toolbar${outlineBoardFiltersPanelOpen ? "" : " hidden"}" aria-label="章节大纲看板筛选">
|
|
8259
|
-
<label class="setting-filter-field outline-board-search-field" for="outline-board-search"><span>搜索章节与摘要</span><input id="outline-board-search" type="search" value="${esc(filterState.query)}" placeholder="章节、目标、冲突、转折或伏笔" autocomplete="off"></label>
|
|
8302
|
+
<label class="setting-filter-field outline-board-search-field" for="outline-board-search"><span>搜索章节与摘要</span><input id="outline-board-search" type="search" value="${esc(filterState.query)}" placeholder="章节、目标、冲突、转折或伏笔" autocomplete="off" maxlength="200"></label>
|
|
8260
8303
|
<label class="setting-filter-field" for="outline-board-volume-filter"><span>按分卷筛选</span><select id="outline-board-volume-filter"><option value="">全部分卷</option>${volumeOptions}</select></label>
|
|
8261
8304
|
<label class="setting-filter-field" for="outline-board-status-filter"><span>按大纲状态筛选</span><select id="outline-board-status-filter"><option value="all" ${filterState.outlineStatus === "all" ? "selected" : ""}>全部大纲状态</option><option value="empty" ${filterState.outlineStatus === "empty" ? "selected" : ""}>尚未规划</option><option value="draft" ${filterState.outlineStatus === "draft" ? "selected" : ""}>草稿</option><option value="ready" ${filterState.outlineStatus === "ready" ? "selected" : ""}>可执行</option><option value="completed" ${filterState.outlineStatus === "completed" ? "selected" : ""}>已完成</option></select></label>
|
|
8262
8305
|
<label class="setting-filter-field" for="outline-board-foreshadow-filter"><span>按伏笔状态筛选</span><select id="outline-board-foreshadow-filter"><option value="all" ${filterState.foreshadowStatus === "all" ? "selected" : ""}>全部伏笔状态</option><option value="none" ${filterState.foreshadowStatus === "none" ? "selected" : ""}>无关联伏笔</option><option value="unresolved" ${filterState.foreshadowStatus === "unresolved" ? "selected" : ""}>有未回收伏笔</option><option value="resolved" ${filterState.foreshadowStatus === "resolved" ? "selected" : ""}>有已回收伏笔</option><option value="abandoned" ${filterState.foreshadowStatus === "abandoned" ? "selected" : ""}>有已放弃伏笔</option></select></label>
|
|
@@ -8266,21 +8309,44 @@ async function renderOutlines(foreshadowPage = moduleListPages.foreshadows) {
|
|
|
8266
8309
|
$("#module-content").innerHTML = `${filterToolbar}<div id="outline-board-results"></div><section class="planning-section outline-foreshadow-library"><div class="section-title"><div><span class="eyebrow">伏笔追踪</span><h2>尚未回收与历史伏笔</h2></div></div>${foreshadowHtml}</section>`;
|
|
8267
8310
|
mountOutlineBoardFilterToggle();
|
|
8268
8311
|
|
|
8269
|
-
const renderOutlineBoardResults = () => {
|
|
8270
|
-
const
|
|
8271
|
-
const
|
|
8272
|
-
const
|
|
8273
|
-
const
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8312
|
+
const renderOutlineBoardResults = (pageResult) => {
|
|
8313
|
+
const currentFilters = normalizeOutlineBoardState(outlineBoardFilters);
|
|
8314
|
+
const filtersActive = Boolean(currentFilters.query.trim() || currentFilters.volumeId || currentFilters.outlineStatus !== "all" || currentFilters.foreshadowStatus !== "all");
|
|
8315
|
+
const controlsActive = filtersActive || currentFilters.sort !== "tree";
|
|
8316
|
+
const summary = `<div class="outline-summary"><article><strong>${filtersActive ? pageResult.total : pageResult.stats.chapterCount}</strong><span>${filtersActive ? `筛选结果 / 共 ${pageResult.stats.chapterCount} 章` : "全书章节"}</span></article><article><strong>${pageResult.stats.outlinedChapterCount}</strong><span>已有章节大纲</span></article><article class="${pageResult.stats.unresolvedForeshadowCount ? "danger-text" : ""}"><strong>${pageResult.stats.unresolvedForeshadowCount}</strong><span>未回收伏笔</span></article></div>`;
|
|
8317
|
+
const boardHtml = pageResult.volumes.length
|
|
8318
|
+
? `<div class="outline-board">${pageResult.volumes.map((volume) => `<section class="outline-board-volume" data-outline-board-volume="${esc(volume.id)}" aria-labelledby="outline-board-volume-${esc(volume.id)}"><header><div><span class="eyebrow">分卷</span><h3 id="outline-board-volume-${esc(volume.id)}">${esc(volume.title)}</h3></div><span>${volume.chapters.length === volume.filteredChapterCount ? `${volume.chapters.length} 章` : `本页 ${volume.chapters.length} / ${volume.filteredChapterCount} 章`}</span></header>${volume.chapters.length ? `<div class="outline-board-grid">${volume.chapters.map((chapter) => outlineBoardCard(chapter, volume)).join("")}</div>` : '<div class="outline-board-volume-empty"><b>本卷暂无章节</b><span>空分卷会保留在全书看板中。</span></div>'}</section>`).join("")}</div>`
|
|
8319
|
+
: emptyModule(pageResult.stats.chapterCount ? "没有符合筛选条件的章节" : pageResult.volumeOptions.length ? "当前分卷还没有章节" : "还没有分卷", pageResult.stats.chapterCount ? "可以调整关键词、状态、分卷或排序条件。" : "先在作品目录创建分卷和章节,再维护每章目标、冲突与转折。");
|
|
8320
|
+
const pagination = renderModulePagination(pageResult, "outlinePlans", "章节大纲看板");
|
|
8321
|
+
$("#outline-board-results").innerHTML = `${summary}<section class="planning-section"><div class="section-title"><div><span class="eyebrow">全书总览</span><h2>章节大纲看板</h2></div><span class="outline-board-result-note">本页 ${pageResult.itemCount} / ${pageResult.total} 章</span></div>${boardHtml}${pagination}</section>`;
|
|
8322
|
+
$("#outline-board-filter-count").textContent = filtersActive ? `筛选后共 ${pageResult.total} 章` : "";
|
|
8278
8323
|
$("#clear-outline-board-filters").disabled = !controlsActive;
|
|
8279
|
-
mountModuleCount(
|
|
8280
|
-
bindOutlineBoardCards(
|
|
8324
|
+
mountModuleCount(pageResult.total + foreshadows.length);
|
|
8325
|
+
bindOutlineBoardCards(pageResult, workId);
|
|
8326
|
+
bindModulePagination("outlinePlans", (page) => refreshOutlineBoard(page));
|
|
8281
8327
|
};
|
|
8282
8328
|
|
|
8283
|
-
const
|
|
8329
|
+
const refreshOutlineBoard = async (page = 1) => {
|
|
8330
|
+
const pageRequestId = ++outlineBoardRenderRequestId;
|
|
8331
|
+
moduleListPages.outlinePlans = page;
|
|
8332
|
+
$("#outline-board-results")?.setAttribute("aria-busy", "true");
|
|
8333
|
+
$("#outline-board-filter-count").textContent = "正在加载看板…";
|
|
8334
|
+
try {
|
|
8335
|
+
const nextBoard = await moduleApi("outlines", outlineBoardRequestPath(workId, outlineBoardFilters, page, boardPageSize));
|
|
8336
|
+
if (state.work?.id !== workId || generation !== workScopedUiGeneration || pageRequestId !== outlineBoardRenderRequestId || state.module !== "outlines") return;
|
|
8337
|
+
board = nextBoard;
|
|
8338
|
+
moduleListPages.outlinePlans = nextBoard.page;
|
|
8339
|
+
renderOutlineBoardResults(nextBoard);
|
|
8340
|
+
} catch (error) {
|
|
8341
|
+
if (state.work?.id !== workId || generation !== workScopedUiGeneration || pageRequestId !== outlineBoardRenderRequestId || state.module !== "outlines") return;
|
|
8342
|
+
$("#outline-board-filter-count").textContent = "看板加载失败";
|
|
8343
|
+
toast(`读取章节大纲看板失败:${error.message}`, "error");
|
|
8344
|
+
} finally {
|
|
8345
|
+
if (pageRequestId === outlineBoardRenderRequestId) $("#outline-board-results")?.removeAttribute("aria-busy");
|
|
8346
|
+
}
|
|
8347
|
+
};
|
|
8348
|
+
|
|
8349
|
+
const updateFilters = (defer = false) => {
|
|
8284
8350
|
Object.assign(outlineBoardFilters, normalizeOutlineBoardState({
|
|
8285
8351
|
query: $("#outline-board-search").value,
|
|
8286
8352
|
volumeId: $("#outline-board-volume-filter").value,
|
|
@@ -8289,10 +8355,16 @@ async function renderOutlines(foreshadowPage = moduleListPages.foreshadows) {
|
|
|
8289
8355
|
sort: $("#outline-board-sort").value
|
|
8290
8356
|
}));
|
|
8291
8357
|
outlineBoardFiltersPanelOpen = true;
|
|
8292
|
-
|
|
8358
|
+
clearTimeout(outlineBoardSearchTimer);
|
|
8359
|
+
if (defer) {
|
|
8360
|
+
outlineBoardSearchTimer = window.setTimeout(() => { void refreshOutlineBoard(1); }, 250);
|
|
8361
|
+
} else {
|
|
8362
|
+
outlineBoardSearchTimer = null;
|
|
8363
|
+
void refreshOutlineBoard(1);
|
|
8364
|
+
}
|
|
8293
8365
|
};
|
|
8294
|
-
$("#outline-board-search").addEventListener("input", updateFilters);
|
|
8295
|
-
["#outline-board-volume-filter", "#outline-board-status-filter", "#outline-board-foreshadow-filter", "#outline-board-sort"].forEach((selector) => $(selector).addEventListener("change", updateFilters));
|
|
8366
|
+
$("#outline-board-search").addEventListener("input", () => updateFilters(true));
|
|
8367
|
+
["#outline-board-volume-filter", "#outline-board-status-filter", "#outline-board-foreshadow-filter", "#outline-board-sort"].forEach((selector) => $(selector).addEventListener("change", () => updateFilters(false)));
|
|
8296
8368
|
$("#clear-outline-board-filters").addEventListener("click", () => {
|
|
8297
8369
|
Object.assign(outlineBoardFilters, normalizeOutlineBoardState());
|
|
8298
8370
|
$("#outline-board-search").value = "";
|
|
@@ -8301,10 +8373,12 @@ async function renderOutlines(foreshadowPage = moduleListPages.foreshadows) {
|
|
|
8301
8373
|
$("#outline-board-foreshadow-filter").value = "all";
|
|
8302
8374
|
$("#outline-board-sort").value = "tree";
|
|
8303
8375
|
outlineBoardFiltersPanelOpen = true;
|
|
8304
|
-
renderOutlineBoardResults();
|
|
8305
8376
|
$("#outline-board-search").focus();
|
|
8377
|
+
clearTimeout(outlineBoardSearchTimer);
|
|
8378
|
+
outlineBoardSearchTimer = null;
|
|
8379
|
+
void refreshOutlineBoard(1);
|
|
8306
8380
|
});
|
|
8307
|
-
renderOutlineBoardResults();
|
|
8381
|
+
renderOutlineBoardResults(board);
|
|
8308
8382
|
bindModuleLayoutToggle(() => renderOutlines(foreshadowPageResult.page));
|
|
8309
8383
|
bindModulePagination("foreshadows", renderOutlines);
|
|
8310
8384
|
$("#module-content").querySelectorAll("[data-edit-foreshadow]").forEach((button) => button.addEventListener("click", () => openForeshadowDialog(foreshadows.find((item) => item.id === button.dataset.editForeshadow))));
|
|
@@ -10145,7 +10219,7 @@ async function renderBookAiSettings() {
|
|
|
10145
10219
|
host.innerHTML = `<section class="config-section">${tokenUsageOverviewMarkup(usage, {
|
|
10146
10220
|
title: "本书 Token 用量",
|
|
10147
10221
|
description: `仅统计《${state.work.title}》迄今产生的 AI Token 消耗与缓存命中情况。`
|
|
10148
|
-
})}</section><section class="config-section"><div class="config-section-header"><div><h2>每日 Token 额度</h2><p>限制本书在后端部署时区(${esc(quotaTimezone)})每个自然日可使用的输入与输出 Token 总量。额度最低为 10,000;达到额度后,新的 AI 请求会等到后端时区的次日零点重置后再执行。</p></div></div><div class="config-inline-save"><label class="checkbox-field config-checkbox-field"><input id="daily-token-quota-enabled" type="checkbox" ${dailyTokenQuota === null ? "" : "checked"}>启用每日额度</label><label class="daily-token-quota-field">每日额度<input id="daily-token-quota" type="number" min="10000" max="2000000000" step="1000" value="${esc(String(dailyTokenQuota ?? 10000))}" aria-label="本书每日 Token 额度" ${dailyTokenQuota === null ? "disabled" : ""}></label><button id="save-daily-token-quota" class="ghost-button config-save-button" type="button">保存</button></div><p id="daily-token-quota-status" class="usage-measurement-note" role="status">${esc(quotaStatusText)}</p></section><section class="config-section"><div class="config-section-header"><div><h2>本书系统提示词</h2><p>会追加在内置系统提示词和平台全局系统提示词之后,只影响《${esc(state.work.title)}》的 AI 请求。</p></div></div><div class="field-label"><textarea id="work-system-prompt" rows="8" aria-label="本书系统提示词" placeholder="例如:叙事使用第三人称,哥斯拉不得离开地球。">${esc(settings.systemPrompt)}</textarea></div><div class="card-actions"><button id="save-work-system-prompt" class="ghost-button config-save-button" type="button">保存本书提示词</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>人物关系拼音索引</h2><p>平时由系统记录增量任务;“同步增量队列”只处理发生变化的来源,“完整重建索引”会将本书全部正文和设定来源重新排队。</p></div></div><div id="relationship-search-index-status" role="status" aria-live="polite">${relationshipIndexStatusMarkup(relationshipIndex)}</div><div class="relationship-index-actions"><button id="sync-relationship-search-index" class="primary-button config-save-button" type="button">同步增量队列</button><button id="refresh-relationship-search-index" class="ghost-button" type="button">刷新状态</button><button id="rebuild-relationship-search-index" class="ghost-button config-save-button" type="button">完整重建索引</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>全书概要引用配额</h2><p>引用全书概要时按分卷保留覆盖,并优先加入与当前问题相关的章节概要;该比例控制概要可使用的上下文预算。</p></div></div><div class="config-inline-save"><label class="book-summary-context-percent-field">上下文占比(%)<input id="book-summary-context-percent" type="number" min="1" max="90" value="${esc(String(settings.bookSummaryContextPercent ?? 50))}" aria-label="全书概要引用上下文占比"></label><button id="save-book-summary-context-percent" class="ghost-button config-save-button" type="button">保存</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>对话上下文 Compact</h2><p
|
|
10222
|
+
})}</section><section class="config-section"><div class="config-section-header"><div><h2>每日 Token 额度</h2><p>限制本书在后端部署时区(${esc(quotaTimezone)})每个自然日可使用的输入与输出 Token 总量。额度最低为 10,000;达到额度后,新的 AI 请求会等到后端时区的次日零点重置后再执行。</p></div></div><div class="config-inline-save"><label class="checkbox-field config-checkbox-field"><input id="daily-token-quota-enabled" type="checkbox" ${dailyTokenQuota === null ? "" : "checked"}>启用每日额度</label><label class="daily-token-quota-field">每日额度<input id="daily-token-quota" type="number" min="10000" max="2000000000" step="1000" value="${esc(String(dailyTokenQuota ?? 10000))}" aria-label="本书每日 Token 额度" ${dailyTokenQuota === null ? "disabled" : ""}></label><button id="save-daily-token-quota" class="ghost-button config-save-button" type="button">保存</button></div><p id="daily-token-quota-status" class="usage-measurement-note" role="status">${esc(quotaStatusText)}</p></section><section class="config-section"><div class="config-section-header"><div><h2>本书系统提示词</h2><p>会追加在内置系统提示词和平台全局系统提示词之后,只影响《${esc(state.work.title)}》的 AI 请求。</p></div></div><div class="field-label"><textarea id="work-system-prompt" rows="8" aria-label="本书系统提示词" placeholder="例如:叙事使用第三人称,哥斯拉不得离开地球。">${esc(settings.systemPrompt)}</textarea></div><div class="card-actions"><button id="save-work-system-prompt" class="ghost-button config-save-button" type="button">保存本书提示词</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>人物关系拼音索引</h2><p>平时由系统记录增量任务;“同步增量队列”只处理发生变化的来源,“完整重建索引”会将本书全部正文和设定来源重新排队。</p></div></div><div id="relationship-search-index-status" role="status" aria-live="polite">${relationshipIndexStatusMarkup(relationshipIndex)}</div><div class="relationship-index-actions"><button id="sync-relationship-search-index" class="primary-button config-save-button" type="button">同步增量队列</button><button id="refresh-relationship-search-index" class="ghost-button" type="button">刷新状态</button><button id="rebuild-relationship-search-index" class="ghost-button config-save-button" type="button">完整重建索引</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>全书概要引用配额</h2><p>引用全书概要时按分卷保留覆盖,并优先加入与当前问题相关的章节概要;该比例控制概要可使用的上下文预算。</p></div></div><div class="config-inline-save"><label class="book-summary-context-percent-field">上下文占比(%)<input id="book-summary-context-percent" type="number" min="1" max="90" value="${esc(String(settings.bookSummaryContextPercent ?? 50))}" aria-label="全书概要引用上下文占比"></label><button id="save-book-summary-context-percent" class="ghost-button config-save-button" type="button">保存</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>对话上下文 Compact</h2><p>该阈值按对话历史的独立预算计算,用于显示可选择压缩或忽略的提醒;整次请求达到模型上下文窗口 95% 时仍会强制压缩较早消息,并尽量保留最近八条原文。</p></div></div><div class="config-inline-save"><label class="context-compact-threshold-field">Compact 阈值(%)<input id="context-compact-threshold" type="number" min="50" max="90" value="${esc(String(settings.contextCompactThreshold ?? 85))}" aria-label="对话上下文 compact 阈值"></label><button id="save-context-compact-threshold" class="ghost-button config-save-button" type="button">保存</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>设定上下文注入</h2><p>开启后,本书的普通 AI 请求会自动注入锁定设定、组织、种族与相关约束;即使本轮同时使用“@注入上下文设定”,也只会注入一次。</p></div></div><div class="config-inline-save"><label class="checkbox-field config-checkbox-field"><input id="always-include-setting-info" type="checkbox" ${settings.alwaysIncludeSettingInfo ? "checked" : ""}>是否注入设定</label><button id="save-always-include-setting-info" class="ghost-button config-save-button" type="button">保存</button></div></section><section class="config-section"><div class="config-section-header"><div><h2>Agent 工具调用上限</h2><p>限制单次回答里 Agent 可调用工具的次数,并用「全局倍数」给整次回答加一道不会因 Compact 重置的熔断阀,防止工具死循环空耗 Token。调用上限 5–48(默认 12);全局倍数 1–6(默认 3,全局上限 = 调用上限 × 倍数)。<a class="config-doc-link" href="https://scriverse.top/docs/global-tool-call-limit.html" target="_blank" rel="noopener noreferrer">了解原理与推荐设置</a></p></div></div><div class="config-inline-save"><label class="agent-tool-call-limit-field">调用上限<input id="agent-tool-call-limit" type="number" min="5" max="48" value="${esc(String(settings.agentToolCallLimit ?? 12))}" aria-label="Agent 工具调用上限"></label><div class="agent-tool-call-global-multiplier-field"><span id="agent-tool-call-global-multiplier-label">全局倍数</span><div class="settings-layout-toggle agent-tool-call-global-multiplier-toggle" role="group" aria-labelledby="agent-tool-call-global-multiplier-label">${[1, 2, 3, 4, 5, 6].map((value) => `<button type="button" data-global-multiplier="${value}" aria-pressed="${Number(settings.agentToolCallGlobalMultiplier ?? 3) === value}">${value}</button>`).join("")}</div><input id="agent-tool-call-global-multiplier" type="hidden" value="${esc(String(Math.min(6, Math.max(1, Number(settings.agentToolCallGlobalMultiplier ?? 3) || 3))))}" aria-label="Agent 工具调用全局倍数"></div><button id="save-agent-tool-call-limit" class="ghost-button config-save-button" type="button">保存</button></div></section><section class="config-section ai-agent-tools-section"><div class="config-section-header"><div><h2>AI 查询工具</h2><p>工具默认可用,作为已有上下文的补充。关闭后模型不会看到对应能力;所有工具只读且有数量、篇幅与调用轮次限制。已开始的对话会锁定创建时的工具集,修改后仅对新对话生效,避免打断 prompt cache。</p></div></div><div class="ai-agent-tools"><label><input name="agent-tool" type="checkbox" value="story_index" ${agentTools.has("story_index") ? "checked" : ""}><span><strong>作品目录与章节概要</strong><small>分页获取卷章、章节 ID 和当前概要,不返回正文。</small></span></label><label><input name="agent-tool" type="checkbox" value="read_chapters" ${agentTools.has("read_chapters") ? "checked" : ""}><span><strong>读取章节</strong><small>按章节 ID 获取概要或正文,每次最多 3 章。</small></span></label><label><input name="agent-tool" type="checkbox" value="search_story_entities" ${agentTools.has("search_story_entities") ? "checked" : ""}><span><strong>搜索作品实体</strong><small>按实体名、拼音或短关键词混合检索设定、人物、组织、时间线、关系、大纲和伏笔;非语义问答。</small></span></label></div><div class="card-actions"><button id="save-agent-tools" class="ghost-button config-save-button" type="button">保存工具设置</button></div></section>${renderTaskDefaults(models, providers, taskDefaults, settings)}`;
|
|
10149
10223
|
const agentToolCallLimitInput = host.querySelector("#agent-tool-call-limit");
|
|
10150
10224
|
agentToolCallLimitInput?.setAttribute("max", String(maximumAgentToolCallLimit));
|
|
10151
10225
|
const agentToolCallDescription = agentToolCallLimitInput?.closest(".config-section")?.querySelector(".config-section-header p");
|
|
@@ -10562,10 +10636,8 @@ function setAiContextDistributionVisible(visible) {
|
|
|
10562
10636
|
}
|
|
10563
10637
|
|
|
10564
10638
|
function showAiContextWarning(usage = null) {
|
|
10565
|
-
|
|
10566
|
-
|
|
10567
|
-
$("#ai-context-warning-title").textContent = percent ? `对话历史已使用 ${percent}% 的独立预算` : "对话历史接近整理阈值";
|
|
10568
|
-
$("#ai-context-warning-message").textContent = `已达到 ${threshold}% 的长期记忆整理阈值。现在可整理较早对话或新开对话;若继续发送,系统会先生成带来源的结构化长期记忆。作品正文超限不会触发此操作。`;
|
|
10639
|
+
$("#ai-context-warning-title").textContent = "对话上下文过长";
|
|
10640
|
+
$("#ai-context-warning-message").textContent = "当前对话上下文过长,是否进行压缩?不压缩可能会导致后续请求失败";
|
|
10569
10641
|
$("#ai-context-warning").classList.remove("hidden");
|
|
10570
10642
|
}
|
|
10571
10643
|
|
|
@@ -10573,6 +10645,10 @@ function hideAiContextWarning() {
|
|
|
10573
10645
|
$("#ai-context-warning").classList.add("hidden");
|
|
10574
10646
|
}
|
|
10575
10647
|
|
|
10648
|
+
function setAiContextWarningActionsDisabled(disabled) {
|
|
10649
|
+
for (const button of $("#ai-context-warning").querySelectorAll("button")) button.disabled = disabled;
|
|
10650
|
+
}
|
|
10651
|
+
|
|
10576
10652
|
async function loadAiReferences() {
|
|
10577
10653
|
const workId = state.work?.id;
|
|
10578
10654
|
if (!workId) return;
|
|
@@ -13342,6 +13418,10 @@ function openModelDialog(providerId, item = null, provider = null) {
|
|
|
13342
13418
|
}
|
|
13343
13419
|
|
|
13344
13420
|
async function sendAi() {
|
|
13421
|
+
return sendAiWithOptions();
|
|
13422
|
+
}
|
|
13423
|
+
|
|
13424
|
+
async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
13345
13425
|
if (!state.work) return toast("请先选择作品", "error");
|
|
13346
13426
|
const composerSnapshot = captureAiPromptComposer();
|
|
13347
13427
|
const instruction = composerSnapshot.text.trim();
|
|
@@ -13414,7 +13494,8 @@ async function sendAi() {
|
|
|
13414
13494
|
scope,
|
|
13415
13495
|
modelId,
|
|
13416
13496
|
citations,
|
|
13417
|
-
conversationId: requestHolder.snapshot.conversationId
|
|
13497
|
+
conversationId: requestHolder.snapshot.conversationId,
|
|
13498
|
+
...(ignoreContextWarning ? { ignoreContextWarning: true } : {})
|
|
13418
13499
|
}, createAiIdempotencyKey());
|
|
13419
13500
|
const request = assertAiRequestCurrent(requestHolder.snapshot);
|
|
13420
13501
|
if (streamed.action === "warn") return;
|
|
@@ -13585,6 +13666,7 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
13585
13666
|
let conversationTitle = null;
|
|
13586
13667
|
let persistedUserMessage = null;
|
|
13587
13668
|
let contextAction = "ready";
|
|
13669
|
+
let warningOnly = false;
|
|
13588
13670
|
let streamContextCompacted = false;
|
|
13589
13671
|
let finalAnswerStarted = false;
|
|
13590
13672
|
const processStartedAt = Date.now();
|
|
@@ -13708,6 +13790,11 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
13708
13790
|
setAiContextMeter(payload.contextUsage);
|
|
13709
13791
|
if (messageMounted) meta.textContent = "已压缩工具上下文,正在继续生成";
|
|
13710
13792
|
} else if (eventName === "complete") {
|
|
13793
|
+
if (payload.warningOnly === true) {
|
|
13794
|
+
warningOnly = true;
|
|
13795
|
+
setAiContextMeter(payload.contextUsage);
|
|
13796
|
+
return;
|
|
13797
|
+
}
|
|
13711
13798
|
mountAssistantMessage();
|
|
13712
13799
|
persistedMessageId = typeof payload.messageId === "string" ? payload.messageId : null;
|
|
13713
13800
|
persistedMessageCreatedAt = typeof payload.messageCreatedAt === "string" ? payload.messageCreatedAt : null;
|
|
@@ -13739,7 +13826,7 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
13739
13826
|
assertAiRequestCurrent(requestHolder.snapshot);
|
|
13740
13827
|
if (streamError) throw streamError;
|
|
13741
13828
|
assertAiStreamCompleted(streamCompleted);
|
|
13742
|
-
return { action: contextAction, content: streamedText, message, metadata: generatedMetadata, messageId: persistedMessageId, createdAt: persistedMessageCreatedAt, conversationTitle, userMessage: persistedUserMessage };
|
|
13829
|
+
return { action: warningOnly ? "warn" : contextAction, content: streamedText, message, metadata: generatedMetadata, messageId: persistedMessageId, createdAt: persistedMessageCreatedAt, conversationTitle, userMessage: persistedUserMessage };
|
|
13743
13830
|
} catch (error) {
|
|
13744
13831
|
const streamFailure = error instanceof Error ? error : new Error(String(error ?? "AI 流式调用失败"));
|
|
13745
13832
|
const interruptionCode = typeof streamFailure.code === "string" ? streamFailure.code.slice(0, 100) : "AI_STREAM_FAILED";
|
|
@@ -15665,7 +15752,7 @@ $("#ai-context-compact").addEventListener("click", async () => {
|
|
|
15665
15752
|
const modelId = $("#ai-model").value;
|
|
15666
15753
|
if (!requestScope || !modelId) return toast("请先选择章节和模型", "error");
|
|
15667
15754
|
const button = $("#ai-context-compact");
|
|
15668
|
-
|
|
15755
|
+
setAiContextWarningActionsDisabled(true);
|
|
15669
15756
|
button.textContent = "压缩中";
|
|
15670
15757
|
try {
|
|
15671
15758
|
const conversationId = await ensureAiConversation();
|
|
@@ -15674,6 +15761,7 @@ $("#ai-context-compact").addEventListener("click", async () => {
|
|
|
15674
15761
|
body: { modelId, scope: requestScope.scope }
|
|
15675
15762
|
});
|
|
15676
15763
|
hideAiContextWarning();
|
|
15764
|
+
$("#ai-prompt").focus();
|
|
15677
15765
|
toast(result.changed ? `已整理 ${result.compactedMessageCount} 条较早消息为长期记忆` : "当前没有需要整理的较早消息");
|
|
15678
15766
|
setAiContextMeter(result.usage);
|
|
15679
15767
|
if (result.changed) {
|
|
@@ -15687,19 +15775,30 @@ $("#ai-context-compact").addEventListener("click", async () => {
|
|
|
15687
15775
|
} catch (error) {
|
|
15688
15776
|
toast(`上下文压缩失败:${error.message}`, "error");
|
|
15689
15777
|
} finally {
|
|
15690
|
-
|
|
15691
|
-
button.textContent = "
|
|
15778
|
+
setAiContextWarningActionsDisabled(false);
|
|
15779
|
+
button.textContent = "压缩";
|
|
15692
15780
|
}
|
|
15693
15781
|
});
|
|
15694
15782
|
$("#ai-context-new-conversation").addEventListener("click", async () => {
|
|
15783
|
+
setAiContextWarningActionsDisabled(true);
|
|
15695
15784
|
try {
|
|
15696
15785
|
await createNewAiConversation();
|
|
15697
15786
|
hideAiContextWarning();
|
|
15698
15787
|
} catch (error) {
|
|
15699
15788
|
toast(error.message, "error");
|
|
15789
|
+
} finally {
|
|
15790
|
+
setAiContextWarningActionsDisabled(false);
|
|
15791
|
+
}
|
|
15792
|
+
});
|
|
15793
|
+
$("#ai-context-dismiss").addEventListener("click", async () => {
|
|
15794
|
+
setAiContextWarningActionsDisabled(true);
|
|
15795
|
+
try {
|
|
15796
|
+
await sendAiWithOptions({ ignoreContextWarning: true });
|
|
15797
|
+
if ($("#ai-context-warning").classList.contains("hidden")) $("#ai-prompt").focus();
|
|
15798
|
+
} finally {
|
|
15799
|
+
setAiContextWarningActionsDisabled(false);
|
|
15700
15800
|
}
|
|
15701
15801
|
});
|
|
15702
|
-
$("#ai-context-dismiss").addEventListener("click", hideAiContextWarning);
|
|
15703
15802
|
$("#ai-history-toggle").addEventListener("click", async () => {
|
|
15704
15803
|
if ($("#ai-history-dialog").open) return setAiHistoryVisible(false);
|
|
15705
15804
|
try {
|
|
@@ -15891,6 +15990,7 @@ document.addEventListener("visibilitychange", () => {
|
|
|
15891
15990
|
if (state.user?.role === "admin" && !systemRestartDetected) void refreshS3BackupEvents();
|
|
15892
15991
|
void refreshSystemHealth();
|
|
15893
15992
|
});
|
|
15993
|
+
window.addEventListener("pagehide", clearToastRegion);
|
|
15894
15994
|
window.addEventListener("beforeunload", (event) => {
|
|
15895
15995
|
if (hasUnsavedEditorChanges()) event.preventDefault();
|
|
15896
15996
|
});
|
package/dist/public/index.html
CHANGED
|
@@ -10,12 +10,12 @@
|
|
|
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=
|
|
13
|
+
<link rel="stylesheet" href="/styles.css?v=20260814-shelf-action-height-v1">
|
|
14
14
|
</head>
|
|
15
15
|
<body class="auth-pending">
|
|
16
16
|
<section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
|
|
17
17
|
<div class="auth-card">
|
|
18
|
-
<div class="auth-brand"><
|
|
18
|
+
<div class="auth-brand"><img class="brand-mark" src="/icon.svg?v=20260712" alt=""><div><strong>叙界</strong><small>小说 AI 创作工作台</small></div></div>
|
|
19
19
|
<span class="eyebrow">账户与作品空间</span>
|
|
20
20
|
<h1 id="auth-title">登录后继续创作</h1>
|
|
21
21
|
<p id="auth-description">你的作品、协作权限和每一次修改都会绑定到账户。</p>
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
<div id="app" class="app-shell">
|
|
66
66
|
<header class="topbar">
|
|
67
67
|
<button id="home-button" class="brand-block" type="button" aria-label="返回书架">
|
|
68
|
-
<
|
|
68
|
+
<img class="brand-mark" src="/icon.svg?v=20260712" alt="">
|
|
69
69
|
<div>
|
|
70
70
|
<strong>叙界</strong>
|
|
71
71
|
<small>小说 AI 创作工作台</small>
|
|
@@ -331,8 +331,8 @@
|
|
|
331
331
|
<div class="prompt-box">
|
|
332
332
|
<div id="ai-citations" class="ai-citations hidden" aria-label="已添加的正文引用"></div>
|
|
333
333
|
<section id="ai-context-warning" class="ai-context-warning hidden" role="status" aria-live="polite">
|
|
334
|
-
<div><strong id="ai-context-warning-title"
|
|
335
|
-
<div><button id="ai-context-compact" type="button"
|
|
334
|
+
<div><strong id="ai-context-warning-title">对话上下文过长</strong><p id="ai-context-warning-message">当前对话上下文过长,是否进行压缩?不压缩可能会导致后续请求失败</p></div>
|
|
335
|
+
<div><button id="ai-context-compact" type="button">压缩</button><button id="ai-context-dismiss" type="button">忽略</button><button id="ai-context-new-conversation" type="button">新开对话</button></div>
|
|
336
336
|
</section>
|
|
337
337
|
<div class="prompt-options">
|
|
338
338
|
<select id="ai-task" aria-label="任务类型">
|
|
@@ -1156,6 +1156,6 @@
|
|
|
1156
1156
|
<div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
|
|
1157
1157
|
<script id="vditorIconScript" src="/vendor/vditor/dist/js/icons/ant.js?v=3.11.2"></script>
|
|
1158
1158
|
<script src="/vendor/vditor/dist/index.min.js?v=3.11.2"></script>
|
|
1159
|
-
<script type="module" src="/app.js?v=
|
|
1159
|
+
<script type="module" src="/app.js?v=20260814-toast-lifecycle-v1"></script>
|
|
1160
1160
|
</body>
|
|
1161
1161
|
</html>
|
|
@@ -30,11 +30,21 @@ export type OutlineBoardVolume<T extends OutlineBoardChapter = OutlineBoardChapt
|
|
|
30
30
|
id?: string | null;
|
|
31
31
|
title?: string | null;
|
|
32
32
|
sortOrder?: number | null;
|
|
33
|
+
chapterCount?: number | null;
|
|
34
|
+
filteredChapterCount?: number | null;
|
|
33
35
|
chapters?: T[];
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
export type OutlineBoard<T extends OutlineBoardChapter = OutlineBoardChapter> = {
|
|
37
39
|
volumes?: Array<OutlineBoardVolume<T>>;
|
|
40
|
+
volumeOptions?: Array<Omit<OutlineBoardVolume<T>, "chapters">>;
|
|
41
|
+
page?: number;
|
|
42
|
+
limit?: number;
|
|
43
|
+
itemCount?: number;
|
|
44
|
+
total?: number;
|
|
45
|
+
pageCount?: number;
|
|
46
|
+
hasMore?: boolean;
|
|
47
|
+
nextPage?: number | null;
|
|
38
48
|
};
|
|
39
49
|
|
|
40
50
|
export type OutlineBoardState = {
|
|
@@ -47,15 +57,11 @@ export type OutlineBoardState = {
|
|
|
47
57
|
|
|
48
58
|
export declare function normalizeOutlineBoardState(value?: Partial<OutlineBoardState>): OutlineBoardState;
|
|
49
59
|
|
|
50
|
-
export declare function
|
|
51
|
-
|
|
52
|
-
value?: Partial<OutlineBoardState
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
totalChapterCount: number;
|
|
57
|
-
visibleChapterCount: number;
|
|
58
|
-
filtersActive: boolean;
|
|
59
|
-
};
|
|
60
|
+
export declare function outlineBoardRequestPath(
|
|
61
|
+
workId: string,
|
|
62
|
+
value?: Partial<OutlineBoardState>,
|
|
63
|
+
page?: number,
|
|
64
|
+
limit?: number
|
|
65
|
+
): string;
|
|
60
66
|
|
|
61
67
|
export declare function outlineBoardUnresolvedCount(chapter: OutlineBoardChapter): number;
|