@musnows/scriverse 0.5.8 → 0.5.9
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 +350 -67
- package/dist/ai.js.map +1 -1
- package/dist/app.js +46 -35
- package/dist/app.js.map +1 -1
- package/dist/cli-core.js +161 -20
- package/dist/cli-core.js.map +1 -1
- package/dist/database.js +72 -0
- package/dist/database.js.map +1 -1
- package/dist/hybrid-search.js +106 -0
- package/dist/hybrid-search.js.map +1 -0
- package/dist/public/app.js +215 -54
- package/dist/public/display-labels.js +13 -1
- package/dist/public/global-search.d.ts +6 -5
- package/dist/public/global-search.js +44 -3
- package/dist/public/index.html +20 -7
- package/dist/public/race-hierarchy.js +0 -35
- package/dist/public/styles.css +24 -5
- package/dist/relationship-search.js +21 -2
- package/dist/relationship-search.js.map +1 -1
- package/dist/store.js +137 -5
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +27 -1
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/public/app.js
CHANGED
|
@@ -33,16 +33,16 @@ import {
|
|
|
33
33
|
taskScopeLabel,
|
|
34
34
|
timelineStatusLabel,
|
|
35
35
|
characterStateFieldLabel
|
|
36
|
-
} from "/display-labels.js?v=
|
|
36
|
+
} from "/display-labels.js?v=20260728-hybrid-search-v1";
|
|
37
37
|
import { parsePageRoute, serializePageRoute } from "/page-route.js?v=20260727-ai-usage";
|
|
38
38
|
import { splitRelationshipKeywordInput, splitRelationshipKeywords, uniqueRelationshipKeywords } from "/relationship-keywords.js?v=20260720-relationship-keyword-chips";
|
|
39
39
|
import { tokenizeVisibleSpaces } from "/whitespace-visualization.js?v=20260718-visible-whitespace";
|
|
40
|
-
import { eligibleRaceParents, orderRaceFilterOptions,
|
|
40
|
+
import { buildRaceForest, eligibleRaceParents, orderRaceFilterOptions, racePathLabel } from "/race-hierarchy.js?v=20260729-race-tree-all-v1";
|
|
41
41
|
import { ANALYSIS_TYPES, analysisTypeDescription } from "/analysis-types.js?v=20260721-analysis-descriptions";
|
|
42
42
|
import { WORK_PERMISSION_MODULES, canReadPermissionModule, canReadUiModule, canWritePermissionModule, canWriteUiModule, emptyModulePermissions, firstReadableUiModule, normalizeModulePermissions, permissionSummary } from "/work-permissions.js?v=20260724-outline-title";
|
|
43
43
|
import { MODULE_LAYOUT_STORAGE_KEY, LEGACY_SETTINGS_LAYOUT_STORAGE_KEY, normalizeModuleLayout } from "/module-layout.js?v=20260723-module-layout-toggle";
|
|
44
44
|
import { isGlobalSearchShortcut } from "/keyboard-shortcuts.js?v=20260723-global-search";
|
|
45
|
-
import { resolveGlobalSearchTarget } from "/global-search.js?v=
|
|
45
|
+
import { resolveGlobalSearchTarget, splitGlobalSearchHighlight } from "/global-search.js?v=20260728-hybrid-search-v1";
|
|
46
46
|
import { filterCharacters, paginateCharacters } from "/character-filters.js?v=20260725-character-filters";
|
|
47
47
|
import { filterRelationships } from "/relationship-filters.js?v=20260726-relationship-filters";
|
|
48
48
|
import { backgroundTaskActivityCount, backgroundTaskPollDelay, collectBackgroundTaskTransitions } from "/background-task-center.js?v=20260726-background-task-center-v1";
|
|
@@ -210,8 +210,12 @@ function renderAnalysisTaskStatus(item) {
|
|
|
210
210
|
const status = normalizedAnalysisTaskStatus(item.status);
|
|
211
211
|
const statusChanged = taskStatusSnapshots.get(taskId) !== status;
|
|
212
212
|
taskStatusSnapshots.set(taskId, status);
|
|
213
|
-
const
|
|
214
|
-
|
|
213
|
+
const waitingToRetry = status === "pending" && Boolean(item.nextAttemptAt);
|
|
214
|
+
const label = waitingToRetry ? "等待重试" : analysisTaskStatusLabel(item.status);
|
|
215
|
+
const retryTitle = waitingToRetry
|
|
216
|
+
? ` title="第 ${esc(String(item.attemptCount ?? 0))} 次尝试失败,将于 ${esc(formatDateTime(item.nextAttemptAt))} 重试"`
|
|
217
|
+
: "";
|
|
218
|
+
return `<span class="task-status-badge is-${status}${statusChanged ? " is-state-change" : ""}" aria-label="任务状态:${esc(label)}"${retryTitle}>
|
|
215
219
|
<span class="task-status-indicator" aria-hidden="true"></span>
|
|
216
220
|
<span>${esc(label)}</span>
|
|
217
221
|
</span>`;
|
|
@@ -362,6 +366,10 @@ let aiReferencesLoadWorkId = null;
|
|
|
362
366
|
let aiConversationsLoadPromise = null;
|
|
363
367
|
let aiConversationsLoadWorkId = null;
|
|
364
368
|
let workScopedUiGeneration = 0;
|
|
369
|
+
let raceHierarchyLoadPromise = null;
|
|
370
|
+
let raceHierarchyLoadWorkId = null;
|
|
371
|
+
let loadedRaceHierarchyWorkId = null;
|
|
372
|
+
let raceListRequestId = 0;
|
|
365
373
|
let importHistoryRecords = [];
|
|
366
374
|
let importHistoryNextPage = null;
|
|
367
375
|
let importHistoryRequestId = 0;
|
|
@@ -862,7 +870,6 @@ let characterListPage = 1;
|
|
|
862
870
|
let taskListPage = 1;
|
|
863
871
|
const moduleListPages = {
|
|
864
872
|
settings: 1,
|
|
865
|
-
races: 1,
|
|
866
873
|
organizations: 1,
|
|
867
874
|
timeline: 1,
|
|
868
875
|
outlinePlans: 1,
|
|
@@ -1364,7 +1371,7 @@ const AI_TOOL_DESCRIPTIONS = {
|
|
|
1364
1371
|
story_index: "分页读取当前作品的卷章目录和章节概要。",
|
|
1365
1372
|
read_chapters: "读取指定章节的概要、正文或两者。",
|
|
1366
1373
|
grep: "查询正文关键字所在的完整段落及章节信息。",
|
|
1367
|
-
search_story_entities: "
|
|
1374
|
+
search_story_entities: "按实体名、拼音或短关键词混合检索设定、人物、组织等结构化记录;非语义问答。",
|
|
1368
1375
|
read_character_sections: "读取指定人物 Markdown 档案章节的摘要或原文。"
|
|
1369
1376
|
};
|
|
1370
1377
|
|
|
@@ -2290,6 +2297,22 @@ function toast(message, type = "info") {
|
|
|
2290
2297
|
}, 3600);
|
|
2291
2298
|
}
|
|
2292
2299
|
|
|
2300
|
+
function persistentToast(message, type = "info") {
|
|
2301
|
+
const region = $("#toast-region");
|
|
2302
|
+
const element = document.createElement("div");
|
|
2303
|
+
element.className = `toast ${type}`;
|
|
2304
|
+
element.setAttribute("role", "status");
|
|
2305
|
+
element.textContent = message;
|
|
2306
|
+
region.append(element);
|
|
2307
|
+
raiseToastRegion();
|
|
2308
|
+
return () => {
|
|
2309
|
+
element.remove();
|
|
2310
|
+
if (!region.childElementCount && typeof region.hidePopover === "function" && region.matches(":popover-open")) {
|
|
2311
|
+
region.hidePopover();
|
|
2312
|
+
}
|
|
2313
|
+
};
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2293
2316
|
function confirmToast(message, { title = "请再次确认", confirmLabel = "确认", cancelLabel = "取消" } = {}) {
|
|
2294
2317
|
const region = $("#toast-region");
|
|
2295
2318
|
const element = document.createElement("section");
|
|
@@ -2887,7 +2910,6 @@ async function openPlatformUiSettingsDialog() {
|
|
|
2887
2910
|
const pageSizes = normalizePageSizes(settings.pageSizes);
|
|
2888
2911
|
$("#page-size-settings").value = String(pageSizes.settings);
|
|
2889
2912
|
$("#page-size-characters").value = String(pageSizes.characters);
|
|
2890
|
-
$("#page-size-races").value = String(pageSizes.races);
|
|
2891
2913
|
$("#page-size-organizations").value = String(pageSizes.organizations);
|
|
2892
2914
|
$("#page-size-timeline").value = String(pageSizes.timeline);
|
|
2893
2915
|
$("#page-size-outlines").value = String(pageSizes.outlines);
|
|
@@ -3010,24 +3032,40 @@ async function openSearchDialog() {
|
|
|
3010
3032
|
}
|
|
3011
3033
|
$("#search-dialog .eyebrow").textContent = `当前作品 · 《${state.work.title}》`;
|
|
3012
3034
|
$("#search-query").value = "";
|
|
3035
|
+
$("#search-type").value = "";
|
|
3013
3036
|
$("#search-results").innerHTML = '<p class="search-results-empty">输入关键词后开始检索。</p>';
|
|
3014
3037
|
$("#search-dialog").showModal();
|
|
3015
3038
|
queueMicrotask(() => $("#search-query").focus());
|
|
3016
3039
|
}
|
|
3017
3040
|
|
|
3018
|
-
function
|
|
3041
|
+
function highlightedSearchText(value, query) {
|
|
3042
|
+
return splitGlobalSearchHighlight(value, query)
|
|
3043
|
+
.map((segment) => segment.match ? `<mark>${esc(segment.text)}</mark>` : esc(segment.text))
|
|
3044
|
+
.join("");
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
function renderSearchResults(results, query) {
|
|
3019
3048
|
if (!results.length) {
|
|
3020
3049
|
$("#search-results").innerHTML = '<p class="search-results-status">未找到相关内容。</p>';
|
|
3021
3050
|
return;
|
|
3022
3051
|
}
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3052
|
+
const matchKindLabel = { metadata: "资料命中", exact: "精确命中", phonetic: "拼音命中" };
|
|
3053
|
+
$("#search-results").innerHTML = `<p class="search-results-summary">找到 ${results.length} 条结果,按综合相关度排序。</p>${results.map((item) => {
|
|
3054
|
+
const matchKinds = Array.isArray(item.matchKinds) ? item.matchKinds : [];
|
|
3055
|
+
const lineRange = Number.isInteger(item.startLine)
|
|
3056
|
+
? `<span class="search-result-chip">${item.startLine === item.endLine ? `第 ${item.startLine} 行` : `第 ${item.startLine}-${item.endLine} 行`}</span>`
|
|
3057
|
+
: "";
|
|
3058
|
+
const subtitle = item.subtitle ? `<small>${esc(item.subtitle)}</small>` : "";
|
|
3059
|
+
return `
|
|
3060
|
+
<button type="button" class="search-result">
|
|
3061
|
+
<div class="search-result-meta"><span>${esc(searchResultTypeLabel(item.type))}</span><strong>${highlightedSearchText(item.title, query)}</strong></div>
|
|
3062
|
+
<p>${highlightedSearchText(item.snippet || "无摘要", query)}</p>
|
|
3063
|
+
<div class="search-result-details">${subtitle}${lineRange}${matchKinds.map((kind) => `<span class="search-result-chip search-result-chip-${esc(kind)}">${esc(matchKindLabel[kind] ?? kind)}</span>`).join("")}</div>
|
|
3064
|
+
</button>`;
|
|
3065
|
+
}).join("")}`;
|
|
3066
|
+
$("#search-results").querySelectorAll(".search-result").forEach((button, index) => {
|
|
3029
3067
|
button.addEventListener("click", () => {
|
|
3030
|
-
openSearchResult(
|
|
3068
|
+
openSearchResult(results[index])
|
|
3031
3069
|
.catch((error) => toast(error.message, "error"));
|
|
3032
3070
|
});
|
|
3033
3071
|
});
|
|
@@ -3041,8 +3079,50 @@ async function runWorkSearch() {
|
|
|
3041
3079
|
return;
|
|
3042
3080
|
}
|
|
3043
3081
|
$("#search-results").innerHTML = '<p class="search-results-status">正在检索……</p>';
|
|
3044
|
-
const
|
|
3045
|
-
|
|
3082
|
+
const parameters = new URLSearchParams({ q: query });
|
|
3083
|
+
const type = $("#search-type").value;
|
|
3084
|
+
if (type) parameters.set("type", type);
|
|
3085
|
+
const results = await api(`/api/works/${encodeURIComponent(state.work.id)}/search?${parameters}`);
|
|
3086
|
+
renderSearchResults(results, query);
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
function revealChapterSearchLines(startLine, endLine) {
|
|
3090
|
+
const start = Math.max(0, Number(startLine) - 1);
|
|
3091
|
+
const end = Math.max(start, Number(endLine ?? startLine) - 1);
|
|
3092
|
+
const input = $("#chapter-content");
|
|
3093
|
+
const selection = selectedChapterLinePayload(start, end);
|
|
3094
|
+
chapterLineSelection = { start: selection.safeStart, end: selection.safeEnd };
|
|
3095
|
+
input.focus({ preventScroll: true });
|
|
3096
|
+
input.setSelectionRange(selection.startOffset, selection.startOffset + selection.text.length);
|
|
3097
|
+
scheduleChapterLineNumbers();
|
|
3098
|
+
requestAnimationFrame(() => {
|
|
3099
|
+
paintChapterLineSelection(selection.safeStart, selection.safeEnd);
|
|
3100
|
+
const row = $("#chapter-line-numbers-inner").querySelector(`[data-line-index="${selection.safeStart}"]`);
|
|
3101
|
+
if (row) input.scrollTop = Math.max(0, row.offsetTop - input.clientHeight / 3);
|
|
3102
|
+
syncChapterLineNumberScroll();
|
|
3103
|
+
});
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
function openSearchEntityPreview(result, item) {
|
|
3107
|
+
const rowsByType = {
|
|
3108
|
+
"timeline-track": [["时间轴名称", item.name], ["简介", item.description], ["排序", item.sortOrder]],
|
|
3109
|
+
"timeline-event": [["事件名称", item.name], ["时间", item.timeLabel], ["类型", item.eventType], ["地点", item.location], ["说明", item.description], ["状态", item.status]],
|
|
3110
|
+
relationship: [["关系", result.title], ["大类", relationshipCategoryLabel(item.category)], ["子类", item.subtype], ["关键词", Array.isArray(item.keywords) ? item.keywords.join("、") : ""], ["当前状态", item.currentStatus], ["置信度", typeof item.confidence === "number" ? `${Math.round(item.confidence * 100)}%` : ""]],
|
|
3111
|
+
"chapter-outline": [["章节", item.chapterTitle], ["本章目标", item.goal], ["核心冲突", item.conflict], ["关键转折", item.turningPoint], ["补充说明", item.notes], ["规划状态", outlineStatusLabel(item.status)]],
|
|
3112
|
+
foreshadow: [["伏笔名称", item.title], ["内容与作用", item.description], ["重要程度", levelLabel(item.importance)], ["状态", foreshadowStatusLabel(item.status)], ["回收结论", item.resolutionNote]]
|
|
3113
|
+
};
|
|
3114
|
+
const rows = rowsByType[result.type] ?? [];
|
|
3115
|
+
const content = rows
|
|
3116
|
+
.filter(([, value]) => value !== undefined && value !== null && String(value).trim())
|
|
3117
|
+
.map(([label, value]) => `<div><dt>${esc(label)}</dt><dd>${esc(value)}</dd></div>`)
|
|
3118
|
+
.join("");
|
|
3119
|
+
openDialog(
|
|
3120
|
+
result.title || searchResultTypeLabel(result.type),
|
|
3121
|
+
`<dl class="search-result-preview">${content || "<div><dt>详情</dt><dd>暂无补充信息</dd></div>"}</dl>`,
|
|
3122
|
+
async () => undefined,
|
|
3123
|
+
searchResultTypeLabel(result.type),
|
|
3124
|
+
{ submitLabel: "关闭", hideCancel: true, wide: true }
|
|
3125
|
+
);
|
|
3046
3126
|
}
|
|
3047
3127
|
|
|
3048
3128
|
async function openSearchResult(result) {
|
|
@@ -3055,6 +3135,10 @@ async function openSearchResult(result) {
|
|
|
3055
3135
|
if (inSettings) await returnFromSettings();
|
|
3056
3136
|
if (target.kind === "chapter") {
|
|
3057
3137
|
await selectChapter(target.id);
|
|
3138
|
+
if (state.chapter?.id === target.id && target.startLine) {
|
|
3139
|
+
revealChapterSearchLines(target.startLine, target.endLine);
|
|
3140
|
+
toast(target.startLine === target.endLine ? `已定位到第 ${target.startLine} 行` : `已定位到第 ${target.startLine}-${target.endLine} 行`);
|
|
3141
|
+
}
|
|
3058
3142
|
return;
|
|
3059
3143
|
}
|
|
3060
3144
|
await showModule(target.module);
|
|
@@ -3064,6 +3148,10 @@ async function openSearchResult(result) {
|
|
|
3064
3148
|
if (target.entity === "character") await openCharacterEditor(item, { readOnly: true });
|
|
3065
3149
|
if (target.entity === "race") await openRaceDialog(item, { readOnly: true });
|
|
3066
3150
|
if (target.entity === "organization") await openOrganizationDialog(item, { readOnly: true });
|
|
3151
|
+
if (target.entity === "review") openReviewDetailDialog(item);
|
|
3152
|
+
if (["timeline-track", "timeline-event", "relationship", "chapter-outline", "foreshadow"].includes(target.entity)) {
|
|
3153
|
+
openSearchEntityPreview(result, item);
|
|
3154
|
+
}
|
|
3067
3155
|
}
|
|
3068
3156
|
|
|
3069
3157
|
async function showSettingsHub() {
|
|
@@ -3192,9 +3280,14 @@ function resetWorkScopedUiCaches() {
|
|
|
3192
3280
|
aiReferencesLoadWorkId = null;
|
|
3193
3281
|
aiConversationsLoadPromise = null;
|
|
3194
3282
|
aiConversationsLoadWorkId = null;
|
|
3283
|
+
raceHierarchyLoadPromise = null;
|
|
3284
|
+
raceHierarchyLoadWorkId = null;
|
|
3285
|
+
loadedRaceHierarchyWorkId = null;
|
|
3286
|
+
raceListRequestId += 1;
|
|
3195
3287
|
state.models = [];
|
|
3196
3288
|
state.characters = [];
|
|
3197
3289
|
state.settings = [];
|
|
3290
|
+
state.races = [];
|
|
3198
3291
|
characterListPage = 1;
|
|
3199
3292
|
Object.keys(moduleListPages).forEach((key) => { moduleListPages[key] = 1; });
|
|
3200
3293
|
relationshipFilters.fromCharacterIds = [];
|
|
@@ -4125,7 +4218,7 @@ async function renderCharacters(page = characterListPage) {
|
|
|
4125
4218
|
const pageSize = pageSizeFor("characters");
|
|
4126
4219
|
const [characterSource, races, organizations] = await Promise.all([
|
|
4127
4220
|
hasCharacterFilters ? apiAllPages(`/api/works/${state.work.id}/characters`) : apiPage(`/api/works/${state.work.id}/characters`, page, pageSize),
|
|
4128
|
-
canReadModule("races") ?
|
|
4221
|
+
canReadModule("races") ? api(`/api/works/${state.work.id}/races`) : Promise.resolve([]),
|
|
4129
4222
|
canReadModule("organizations") ? apiAllPages(`/api/works/${state.work.id}/organizations`) : Promise.resolve([])
|
|
4130
4223
|
]);
|
|
4131
4224
|
const characterPage = hasCharacterFilters
|
|
@@ -4228,15 +4321,16 @@ async function renderCharacters(page = characterListPage) {
|
|
|
4228
4321
|
$("#module-content").querySelectorAll("[data-edit-character]").forEach((button) => button.addEventListener("click", () => openCharacterEditor(pageCharacters.find((item) => item.id === button.dataset.editCharacter))));
|
|
4229
4322
|
}
|
|
4230
4323
|
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
mountModuleCount(state.races.length);
|
|
4324
|
+
function renderRaceCollection(total, descendantsLoading = false) {
|
|
4325
|
+
mountModuleCount(total);
|
|
4234
4326
|
const layout = readModuleLayout();
|
|
4235
|
-
const
|
|
4236
|
-
?
|
|
4237
|
-
:
|
|
4238
|
-
moduleListPages.races = pageResult.page;
|
|
4327
|
+
const raceItems = layout === "rows"
|
|
4328
|
+
? [...state.races].sort((left, right) => String(left.name).localeCompare(String(right.name), "zh-CN"))
|
|
4329
|
+
: buildRaceForest(state.races);
|
|
4239
4330
|
const canEditRaces = canEditModule("races");
|
|
4331
|
+
const directChildCount = (item) => Number.isInteger(Number(item.childCount))
|
|
4332
|
+
? Number(item.childCount)
|
|
4333
|
+
: Array.isArray(item.children) ? item.children.length : 0;
|
|
4240
4334
|
const raceActions = (item) => canEditRaces
|
|
4241
4335
|
? recordCardEditButton("edit-race", item.id, `种族“${item.name}”`)
|
|
4242
4336
|
: recordHistoryButton("race", item.id, item.name);
|
|
@@ -4244,7 +4338,7 @@ async function renderRaces(page = moduleListPages.races) {
|
|
|
4244
4338
|
? raceActions(item)
|
|
4245
4339
|
: `<div class="card-actions">${raceActions(item)}</div>`;
|
|
4246
4340
|
const renderRaceNode = (item) => `<details class="race-tree-node"${state.collapsedRaceIds.has(item.id) ? "" : " open"} data-race-node="${esc(item.id)}">
|
|
4247
|
-
<summary><span>${esc(item.name)}</span><small>${item
|
|
4341
|
+
<summary><span>${esc(item.name)}</span><small>${directChildCount(item)} 个直接子种族</small></summary>
|
|
4248
4342
|
<div class="race-tree-branch">
|
|
4249
4343
|
<article class="record-card race-card preview-record-card${canEditRaces ? " has-card-edit" : ""}" data-open-race="${esc(item.id)}" role="button" tabindex="0" aria-label="查看种族 ${esc(item.name)}"><small>${item.memberIds.length} 位直接角色 · ${item.settingsCount ?? item.settings?.length ?? 0} 条自身设定</small>
|
|
4250
4344
|
<div class="race-path" aria-label="种族路径">${esc(racePathLabel(item))}</div>
|
|
@@ -4256,9 +4350,9 @@ async function renderRaces(page = moduleListPages.races) {
|
|
|
4256
4350
|
${item.children.length ? `<div class="race-tree-children">${item.children.map(renderRaceNode).join("")}</div>` : ""}
|
|
4257
4351
|
</div>
|
|
4258
4352
|
</details>`;
|
|
4259
|
-
const raceRows = () => `<div class="module-row-list">${
|
|
4353
|
+
const raceRows = () => `<div class="module-row-list">${raceItems.map((item) => {
|
|
4260
4354
|
const preview = moduleRowPreview(item.description || "尚未填写种族简介");
|
|
4261
|
-
const meta = `${item.memberIds.length} 位直接角色 · ${(item.settingsCount ?? item.settings?.length ?? 0) ? "已填写共同设定" : "暂无共同设定"}`;
|
|
4355
|
+
const meta = `${directChildCount(item)} 个直接子种族 · ${item.memberIds.length} 位直接角色 · ${(item.settingsCount ?? item.settings?.length ?? 0) ? "已填写共同设定" : "暂无共同设定"}`;
|
|
4262
4356
|
return `
|
|
4263
4357
|
<article class="record-card module-row race-card preview-record-card" data-open-race="${esc(item.id)}" role="button" tabindex="0" aria-label="查看种族 ${esc(item.name)}">
|
|
4264
4358
|
<small>${esc(meta)}</small>
|
|
@@ -4270,15 +4364,65 @@ async function renderRaces(page = moduleListPages.races) {
|
|
|
4270
4364
|
if (state.races.length) mountModuleLayoutToggle(layout, "种族列表样式");
|
|
4271
4365
|
if (state.races.length && layout !== "rows") mountRaceTreeExpandToggle();
|
|
4272
4366
|
$("#module-content").innerHTML = state.races.length
|
|
4273
|
-
? `${layout === "rows" ? raceRows() : `<section class="race-tree" aria-label="种族层级">${
|
|
4367
|
+
? `${layout === "rows" ? raceRows() : `<section class="race-tree" aria-label="种族层级" aria-busy="${descendantsLoading}">${raceItems.map(renderRaceNode).join("")}</section>`}`
|
|
4274
4368
|
: emptyModule("还没有种族档案", "先创建种族及共同设定,之后角色编辑器才能选择该种族。");
|
|
4275
|
-
bindModuleLayoutToggle(() =>
|
|
4276
|
-
bindModulePagination("races", renderRaces);
|
|
4369
|
+
bindModuleLayoutToggle(() => renderRaceCollection(total, descendantsLoading));
|
|
4277
4370
|
bindRaceTreeExpandToggle();
|
|
4278
4371
|
bindRaceTreeNodeToggles();
|
|
4279
4372
|
const openRace = async (id, readOnly) => openRaceDialog(await api(`/api/races/${encodeURIComponent(id)}`), { readOnly });
|
|
4280
4373
|
$("#module-content").querySelectorAll("[data-edit-race]").forEach((button) => button.addEventListener("click", () => { void openRace(button.dataset.editRace, false); }));
|
|
4281
|
-
bindEntityHistoryButtons(async () => { await renderRaces(
|
|
4374
|
+
bindEntityHistoryButtons(async () => { await renderRaces(); await loadAiReferences(); });
|
|
4375
|
+
}
|
|
4376
|
+
|
|
4377
|
+
async function ensureCompleteRaceList() {
|
|
4378
|
+
const workId = state.work?.id;
|
|
4379
|
+
if (!workId) return false;
|
|
4380
|
+
const generation = workScopedUiGeneration;
|
|
4381
|
+
if (loadedRaceHierarchyWorkId === workId) return true;
|
|
4382
|
+
if (raceHierarchyLoadPromise && raceHierarchyLoadWorkId === workId) {
|
|
4383
|
+
await raceHierarchyLoadPromise.catch(() => {});
|
|
4384
|
+
}
|
|
4385
|
+
if (state.work?.id !== workId || generation !== workScopedUiGeneration) return false;
|
|
4386
|
+
if (loadedRaceHierarchyWorkId === workId) return true;
|
|
4387
|
+
const races = await api(`/api/works/${workId}/races`);
|
|
4388
|
+
if (state.work?.id !== workId || generation !== workScopedUiGeneration) return false;
|
|
4389
|
+
state.races = races;
|
|
4390
|
+
loadedRaceHierarchyWorkId = workId;
|
|
4391
|
+
return true;
|
|
4392
|
+
}
|
|
4393
|
+
|
|
4394
|
+
async function renderRaces() {
|
|
4395
|
+
const workId = state.work.id;
|
|
4396
|
+
const generation = workScopedUiGeneration;
|
|
4397
|
+
const requestId = ++raceListRequestId;
|
|
4398
|
+
const roots = await api(`/api/works/${workId}/races?scope=roots`);
|
|
4399
|
+
if (state.work?.id !== workId || generation !== workScopedUiGeneration || requestId !== raceListRequestId) return;
|
|
4400
|
+
state.races = roots.items;
|
|
4401
|
+
loadedRaceHierarchyWorkId = roots.items.length === roots.total ? workId : null;
|
|
4402
|
+
renderRaceCollection(roots.total, loadedRaceHierarchyWorkId !== workId);
|
|
4403
|
+
if (loadedRaceHierarchyWorkId === workId) return;
|
|
4404
|
+
|
|
4405
|
+
const dismissLoadingToast = persistentToast("正在加载子种族……");
|
|
4406
|
+
const loadPromise = api(`/api/works/${workId}/races?scope=descendants`).then((descendants) => {
|
|
4407
|
+
if (state.work?.id !== workId || generation !== workScopedUiGeneration || requestId !== raceListRequestId) return;
|
|
4408
|
+
state.races = [...roots.items, ...descendants];
|
|
4409
|
+
loadedRaceHierarchyWorkId = workId;
|
|
4410
|
+
if (state.module === "races") renderRaceCollection(roots.total);
|
|
4411
|
+
});
|
|
4412
|
+
raceHierarchyLoadPromise = loadPromise;
|
|
4413
|
+
raceHierarchyLoadWorkId = workId;
|
|
4414
|
+
void loadPromise.catch((error) => {
|
|
4415
|
+
if (state.work?.id === workId && generation === workScopedUiGeneration && requestId === raceListRequestId) {
|
|
4416
|
+
renderRaceCollection(roots.total);
|
|
4417
|
+
toast(`父种族已显示,但子种族载入失败:${error.message}`, "error");
|
|
4418
|
+
}
|
|
4419
|
+
}).finally(() => {
|
|
4420
|
+
if (raceHierarchyLoadPromise === loadPromise) {
|
|
4421
|
+
raceHierarchyLoadPromise = null;
|
|
4422
|
+
raceHierarchyLoadWorkId = null;
|
|
4423
|
+
}
|
|
4424
|
+
dismissLoadingToast();
|
|
4425
|
+
});
|
|
4282
4426
|
}
|
|
4283
4427
|
|
|
4284
4428
|
async function renderOrganizations(page = moduleListPages.organizations) {
|
|
@@ -4619,7 +4763,7 @@ async function renderTasks(page = taskListPage) {
|
|
|
4619
4763
|
apiPage(`/api/works/${state.work.id}/tasks`, page, pageSize),
|
|
4620
4764
|
canReadModule("ai-settings")
|
|
4621
4765
|
? api(`/api/works/${state.work.id}/ai-settings`)
|
|
4622
|
-
: Promise.resolve({ autoRunEnabled: false, autoRunConcurrency: 2,
|
|
4766
|
+
: Promise.resolve({ autoRunEnabled: false, autoRunConcurrency: 2, autoRunDailyTaskLimit: 0, autoRunFailureThreshold: 3, autoRunPaused: false })
|
|
4623
4767
|
]);
|
|
4624
4768
|
if (!taskPage.items.length && page > 1) return renderTasks(page - 1);
|
|
4625
4769
|
taskListPage = taskPage.page;
|
|
@@ -4631,6 +4775,12 @@ async function renderTasks(page = taskListPage) {
|
|
|
4631
4775
|
const runningCount = Number(taskPage.stats?.runningCount ?? 0);
|
|
4632
4776
|
const activeTaskCount = pendingCount + runningCount;
|
|
4633
4777
|
const runningProgress = runningCount ? analysisTaskProgressValue(taskPage.stats?.runningProgress) : 0;
|
|
4778
|
+
const autoRunPaused = Boolean(settings.autoRunEnabled && settings.autoRunPaused);
|
|
4779
|
+
const autoRunActive = Boolean(settings.autoRunEnabled && !autoRunPaused);
|
|
4780
|
+
const queueProgressLabel = runningCount
|
|
4781
|
+
? "运行中任务平均进度"
|
|
4782
|
+
: autoRunPaused ? "自动执行已暂停" : autoRunActive ? "等待任务开始" : "自动执行已关闭";
|
|
4783
|
+
const queueProgressClass = runningCount ? "is-running" : autoRunPaused ? "is-paused" : "is-waiting";
|
|
4634
4784
|
const visibleTaskIds = new Set(tasks.map((item) => String(item.id)));
|
|
4635
4785
|
for (const taskId of taskStatusSnapshots.keys()) {
|
|
4636
4786
|
if (!visibleTaskIds.has(taskId)) taskStatusSnapshots.delete(taskId);
|
|
@@ -4647,27 +4797,30 @@ async function renderTasks(page = taskListPage) {
|
|
|
4647
4797
|
<div class="task-auto-run-copy">
|
|
4648
4798
|
<strong id="task-auto-run-title">自动执行待分析任务</strong>
|
|
4649
4799
|
<small>只执行已经进入“待执行”队列的任务,不会自动创建人物关系、世界观或其他分析。</small>
|
|
4650
|
-
<small
|
|
4800
|
+
<small>开启后会持续执行直到队列清空;临时错误自动退避重试,连续失败达到阈值后暂停。</small>
|
|
4651
4801
|
</div>
|
|
4652
4802
|
<div class="task-auto-run-controls">
|
|
4653
|
-
<label class="checkbox-field"><input id="task-auto-run-enabled" type="checkbox" ${settings.autoRunEnabled ? "checked" : ""}><span
|
|
4803
|
+
<label class="checkbox-field"><input id="task-auto-run-enabled" type="checkbox" ${settings.autoRunEnabled ? "checked" : ""}><span>持续自动执行</span></label>
|
|
4654
4804
|
<label>同时运行上限<input id="task-auto-run-concurrency" type="number" min="1" max="8" value="${esc(String(settings.autoRunConcurrency ?? 2))}"></label>
|
|
4655
|
-
<label
|
|
4805
|
+
<label>每日任务上限<input id="task-auto-run-daily-limit" type="number" min="0" max="10000" value="${esc(String(settings.autoRunDailyTaskLimit ?? 0))}" aria-describedby="task-auto-run-daily-help"></label>
|
|
4806
|
+
<label>连续失败暂停阈值<input id="task-auto-run-failure-threshold" type="number" min="1" max="10" value="${esc(String(settings.autoRunFailureThreshold ?? 3))}"></label>
|
|
4656
4807
|
<button id="task-auto-run-save" class="primary-button" type="button">保存并生效</button>
|
|
4657
|
-
<button id="task-auto-run-
|
|
4808
|
+
<button id="task-auto-run-toggle" class="ghost-button" type="button" ${settings.autoRunEnabled ? "" : "disabled"}>${autoRunPaused ? "恢复自动执行" : "暂停自动执行"}</button>
|
|
4658
4809
|
</div>
|
|
4659
|
-
<p class="task-auto-run-
|
|
4810
|
+
<p id="task-auto-run-daily-help" class="task-auto-run-help">每日任务上限填 0 表示不限制;达到上限后会在下一个 UTC 自然日自动恢复。</p>
|
|
4811
|
+
<p class="task-auto-run-meta">待执行队列 ${pendingCount} 个 · 正在运行 ${runningCount} 个 · ${autoRunPaused ? "已暂停" : autoRunActive ? "持续执行中" : "未开启"}</p>
|
|
4812
|
+
${autoRunPaused ? `<p class="task-auto-run-alert" role="status"><strong>自动执行已暂停</strong><span>${esc(settings.autoRunPauseReason || "需要人工确认后恢复")}</span>${settings.autoRunResumeAt ? `<small>预计 ${esc(formatDateTime(settings.autoRunResumeAt))} 自动恢复</small>` : ""}</p>` : ""}
|
|
4660
4813
|
<div class="task-auto-run-progress ${activeTaskCount ? "" : "hidden"}" aria-live="polite">
|
|
4661
|
-
<div class="task-auto-run-progress-ring ${
|
|
4814
|
+
<div class="task-auto-run-progress-ring ${queueProgressClass}" role="progressbar" aria-label="${queueProgressLabel}" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${runningProgress}">
|
|
4662
4815
|
<svg viewBox="0 0 120 120" aria-hidden="true" focusable="false">
|
|
4663
4816
|
<circle class="task-auto-run-progress-ring-track" cx="60" cy="60" r="52"></circle>
|
|
4664
4817
|
<circle class="task-auto-run-progress-ring-value" cx="60" cy="60" r="52" pathLength="100" stroke-dasharray="${runningProgress} 100"></circle>
|
|
4665
4818
|
</svg>
|
|
4666
|
-
<div class="task-auto-run-progress-ring-label"><strong>${runningProgress}%</strong><span>${
|
|
4819
|
+
<div class="task-auto-run-progress-ring-label"><strong>${runningProgress}%</strong><span>${queueProgressLabel}</span></div>
|
|
4667
4820
|
</div>
|
|
4668
4821
|
<div class="task-auto-run-progress-bar-layout">
|
|
4669
|
-
<div class="task-auto-run-progress-label"><span>${
|
|
4670
|
-
<progress class="task-auto-run-progress-bar ${
|
|
4822
|
+
<div class="task-auto-run-progress-label"><span>${queueProgressLabel}</span><strong>${runningProgress}%</strong></div>
|
|
4823
|
+
<progress class="task-auto-run-progress-bar ${queueProgressClass}" max="100" value="${runningProgress}" aria-label="${queueProgressLabel}">${runningProgress}%</progress>
|
|
4671
4824
|
</div>
|
|
4672
4825
|
</div>
|
|
4673
4826
|
</section>
|
|
@@ -4680,7 +4833,7 @@ async function renderTasks(page = taskListPage) {
|
|
|
4680
4833
|
<td class="task-progress-cell">${renderAnalysisTaskProgress(item)}</td>
|
|
4681
4834
|
<td class="task-row-actions">
|
|
4682
4835
|
<button class="ghost-button" type="button" data-task-detail="${esc(item.id)}">详情</button>
|
|
4683
|
-
${item.status === "pending" ? `<button class="ghost-button" type="button" data-run-task="${esc(item.id)}">运行</button>` : ""}
|
|
4836
|
+
${item.status === "pending" && !item.nextAttemptAt ? `<button class="ghost-button" type="button" data-run-task="${esc(item.id)}">运行</button>` : ""}
|
|
4684
4837
|
${item.status === "pending" || item.status === "running" ? `<button class="ghost-button" type="button" data-cancel-task="${esc(item.id)}">取消</button>` : ""}
|
|
4685
4838
|
</td>
|
|
4686
4839
|
</tr>`).join("")}</tbody></table>${pagination}` : emptyModule("还没有 AI 分析记录", "点击“开始 AI 分析”,可分析指定章节或整部作品。")}`;
|
|
@@ -4700,26 +4853,34 @@ async function renderTasks(page = taskListPage) {
|
|
|
4700
4853
|
body: {
|
|
4701
4854
|
autoRunEnabled: $("#task-auto-run-enabled").checked,
|
|
4702
4855
|
autoRunConcurrency: Number($("#task-auto-run-concurrency").value),
|
|
4703
|
-
|
|
4856
|
+
autoRunDailyTaskLimit: Number($("#task-auto-run-daily-limit").value),
|
|
4857
|
+
autoRunFailureThreshold: Number($("#task-auto-run-failure-threshold").value)
|
|
4704
4858
|
}
|
|
4705
4859
|
});
|
|
4706
4860
|
toast(updated.autoRunEnabled
|
|
4707
|
-
?
|
|
4861
|
+
? `持续自动执行已开启:同时最多 ${updated.autoRunConcurrency} 个`
|
|
4708
4862
|
: "自动执行已关闭");
|
|
4709
4863
|
await renderTasks();
|
|
4864
|
+
window.setTimeout(() => $("#task-auto-run-save")?.focus(), 0);
|
|
4710
4865
|
} catch (error) {
|
|
4711
4866
|
toast(error.message, "error");
|
|
4712
4867
|
button.disabled = false;
|
|
4713
4868
|
}
|
|
4714
4869
|
});
|
|
4715
|
-
$("#task-auto-run-
|
|
4716
|
-
const button = $("#task-auto-run-
|
|
4870
|
+
$("#task-auto-run-toggle")?.addEventListener("click", async () => {
|
|
4871
|
+
const button = $("#task-auto-run-toggle");
|
|
4717
4872
|
button.disabled = true;
|
|
4718
4873
|
try {
|
|
4719
|
-
|
|
4720
|
-
|
|
4874
|
+
if (autoRunPaused) {
|
|
4875
|
+
await api(`/api/works/${state.work.id}/tasks/auto-run`, { method: "POST", body: {} });
|
|
4876
|
+
toast("自动执行已恢复");
|
|
4877
|
+
} else {
|
|
4878
|
+
await api(`/api/works/${state.work.id}/ai-settings`, { method: "PATCH", body: { autoRunEnabled: false } });
|
|
4879
|
+
toast("已暂停启动新的分析任务,正在运行的任务会继续完成");
|
|
4880
|
+
}
|
|
4721
4881
|
await refreshBackgroundTaskCenter({ announce: false });
|
|
4722
4882
|
await renderTasks();
|
|
4883
|
+
window.setTimeout(() => $(autoRunPaused ? "#task-auto-run-toggle" : "#task-auto-run-enabled")?.focus(), 0);
|
|
4723
4884
|
} catch (error) {
|
|
4724
4885
|
toast(error.message, "error");
|
|
4725
4886
|
button.disabled = false;
|
|
@@ -4776,7 +4937,7 @@ async function renderTasks(page = taskListPage) {
|
|
|
4776
4937
|
button.disabled = false;
|
|
4777
4938
|
}
|
|
4778
4939
|
}));
|
|
4779
|
-
scheduleTaskProgressRefresh(state.work.id, runningCount);
|
|
4940
|
+
scheduleTaskProgressRefresh(state.work.id, runningCount > 0 || (pendingCount > 0 && autoRunActive) ? 1 : 0);
|
|
4780
4941
|
}
|
|
4781
4942
|
|
|
4782
4943
|
async function rerunAnalysisTask(taskId, button, { closeDetail = false } = {}) {
|
|
@@ -5703,7 +5864,7 @@ async function renderBookAiSettings() {
|
|
|
5703
5864
|
host.innerHTML = `<section class="config-section">${tokenUsageOverviewMarkup(usage, {
|
|
5704
5865
|
title: "本书 Token 用量",
|
|
5705
5866
|
description: `仅统计《${state.work.title}》迄今产生的 AI Token 消耗与缓存命中情况。`
|
|
5706
|
-
})}</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>对话 context 使用独立预算。达到该百分比阈值时先提醒;继续发送会对较早消息执行 compact,压缩上下文占用,并尽量保留最近八条原文。</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>AI 查询工具</h2><p>工具默认可用,作为已有上下文的补充。关闭后模型不会看到对应能力;所有工具只读且有数量、篇幅与调用轮次限制。</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
|
|
5867
|
+
})}</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>对话 context 使用独立预算。达到该百分比阈值时先提醒;继续发送会对较早消息执行 compact,压缩上下文占用,并尽量保留最近八条原文。</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>AI 查询工具</h2><p>工具默认可用,作为已有上下文的补充。关闭后模型不会看到对应能力;所有工具只读且有数量、篇幅与调用轮次限制。</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)}`;
|
|
5707
5868
|
scrollUsageCalendarsToLatest(host);
|
|
5708
5869
|
host.querySelector('input[name="agent-tool"][value="search_story_entities"]').closest("label").insertAdjacentHTML(
|
|
5709
5870
|
"beforebegin",
|
|
@@ -7307,7 +7468,7 @@ async function showCharacterHistory() {
|
|
|
7307
7468
|
async function openCharacterEditor(item = null, { readOnly = false } = {}) {
|
|
7308
7469
|
entityEditorReadOnly = readOnly;
|
|
7309
7470
|
[state.races, state.organizations, state.characters] = await Promise.all([
|
|
7310
|
-
canReadModule("races") ?
|
|
7471
|
+
canReadModule("races") ? api(`/api/works/${state.work.id}/races`) : Promise.resolve([]),
|
|
7311
7472
|
canReadModule("organizations") ? apiAllPages(`/api/works/${state.work.id}/organizations`) : Promise.resolve([]),
|
|
7312
7473
|
canReadModule("characters") ? apiAllPages(`/api/works/${state.work.id}/characters`) : Promise.resolve([])
|
|
7313
7474
|
]);
|
|
@@ -7440,6 +7601,7 @@ function renderKnowledgeEditorFields(kind, item, memberOptions, parentOptions) {
|
|
|
7440
7601
|
async function openKnowledgeEditor(kind, item, { readOnly = false } = {}) {
|
|
7441
7602
|
entityEditorReadOnly = readOnly;
|
|
7442
7603
|
await discardPendingMarkdownAttachments();
|
|
7604
|
+
if (kind === "race" && !(await ensureCompleteRaceList())) return;
|
|
7443
7605
|
state.characters = canReadModule("characters") ? await apiAllPages(`/api/works/${state.work.id}/characters`) : [];
|
|
7444
7606
|
const memberOptions = state.characters.map((character) => [character.id, `${character.name}${character.aliases.length ? `(${character.aliases.join("、")})` : ""}`]);
|
|
7445
7607
|
const isRace = kind === "race";
|
|
@@ -9209,7 +9371,6 @@ $("#platform-ui-settings-form").addEventListener("submit", async (event) => {
|
|
|
9209
9371
|
pageSizes: {
|
|
9210
9372
|
settings: Number($("#page-size-settings").value),
|
|
9211
9373
|
characters: Number($("#page-size-characters").value),
|
|
9212
|
-
races: Number($("#page-size-races").value),
|
|
9213
9374
|
organizations: Number($("#page-size-organizations").value),
|
|
9214
9375
|
timeline: Number($("#page-size-timeline").value),
|
|
9215
9376
|
outlines: Number($("#page-size-outlines").value),
|
|
@@ -81,7 +81,19 @@ export function occurrenceRoleLabel(value) {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
export function searchResultTypeLabel(value) {
|
|
84
|
-
return enumLabel({
|
|
84
|
+
return enumLabel({
|
|
85
|
+
chapter: "章节",
|
|
86
|
+
setting: "设定",
|
|
87
|
+
character: "角色",
|
|
88
|
+
race: "种族",
|
|
89
|
+
organization: "组织",
|
|
90
|
+
"timeline-track": "独立时间轴",
|
|
91
|
+
"timeline-event": "时间线事件",
|
|
92
|
+
relationship: "人物关系",
|
|
93
|
+
"chapter-outline": "章节大纲",
|
|
94
|
+
foreshadow: "伏笔",
|
|
95
|
+
review: "审核项"
|
|
96
|
+
}, value, "其他资料");
|
|
85
97
|
}
|
|
86
98
|
|
|
87
99
|
export function characterStateFieldLabel(value) {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
export type GlobalSearchTarget =
|
|
2
|
-
| { kind: "chapter"; type: "chapter"; id: string; module: "editor" }
|
|
2
|
+
| { kind: "chapter"; type: "chapter"; id: string; module: "editor"; startLine?: number; endLine?: number }
|
|
3
3
|
| {
|
|
4
4
|
kind: "entity";
|
|
5
|
-
type: "setting" | "character" | "race" | "organization";
|
|
5
|
+
type: "setting" | "character" | "race" | "organization" | "timeline-track" | "timeline-event" | "relationship" | "chapter-outline" | "foreshadow" | "review";
|
|
6
6
|
id: string;
|
|
7
|
-
module: "settings" | "characters" | "races" | "organizations";
|
|
8
|
-
entity: "setting" | "character" | "race" | "organization";
|
|
7
|
+
module: "settings" | "characters" | "races" | "organizations" | "timeline" | "relationships" | "outlines" | "reviews";
|
|
8
|
+
entity: "setting" | "character" | "race" | "organization" | "timeline-track" | "timeline-event" | "relationship" | "chapter-outline" | "foreshadow" | "review";
|
|
9
9
|
apiPath: string;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
export function
|
|
12
|
+
export function splitGlobalSearchHighlight(value: unknown, query: unknown): Array<{ text: string; match: boolean }>;
|
|
13
|
+
export function resolveGlobalSearchTarget(result?: { type?: unknown; id?: unknown; startLine?: unknown; endLine?: unknown }): GlobalSearchTarget | null;
|