@musnows/scriverse 0.8.0 → 0.8.2
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-conversation-export.js +3 -1
- package/dist/ai-conversation-export.js.map +1 -1
- package/dist/ai.js +72 -6
- package/dist/ai.js.map +1 -1
- package/dist/app.js +48 -14
- package/dist/app.js.map +1 -1
- package/dist/database.js +26 -2
- package/dist/database.js.map +1 -1
- package/dist/public/app.js +326 -72
- package/dist/public/index.html +12 -7
- package/dist/public/relationship-graph.js +11 -3
- package/dist/public/stream-typewriter.d.ts +1 -0
- package/dist/public/stream-typewriter.js +12 -0
- package/dist/public/styles.css +33 -17
- package/dist/public/work-permissions.d.ts +1 -1
- package/dist/public/work-permissions.js +13 -1
- package/dist/store.js +90 -16
- 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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { buildRelationshipGraph, createGalaxyRenderer, normalizeGalaxyFrameRate, normalizeGalaxyMotionMode, renderRelationshipMindMap } from "/relationship-graph.js?v=20260817-relationship-canvas-scale-v1&feature=galaxy-motion-mode-v3";
|
|
1
|
+
import { buildRelationshipGraph, createGalaxyRenderer, normalizeGalaxyFrameRate, normalizeGalaxyMotionMode, renderRelationshipMindMap } from "/relationship-graph.js?v=20260817-relationship-canvas-scale-v1&feature=galaxy-motion-mode-v3&feature=galaxy-edge-label-threshold-v1";
|
|
2
2
|
import { collapseExcessBlankLines, formatDateTime, normalizeParagraphSpacing } from "/text-formatting.js?v=20260713-saved-at-seconds";
|
|
3
3
|
import { renderMarkdown } from "/markdown.js?v=20260731-no-external-images-v1";
|
|
4
4
|
import { findAiMention, listAiMentionOptions, mergeAiReferenceScope, userMessageMentionNames } from "/ai-mentions.js?v=20260811-user-message-mentions-v1";
|
|
@@ -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";
|
|
@@ -178,6 +178,7 @@ const state = {
|
|
|
178
178
|
aiConversationModelId: null,
|
|
179
179
|
aiConversations: [],
|
|
180
180
|
aiRoleplayCharacter: null,
|
|
181
|
+
aiRoleplayUserCharacter: null,
|
|
181
182
|
aiLastMessageAt: null,
|
|
182
183
|
settings: [],
|
|
183
184
|
dirty: false,
|
|
@@ -230,6 +231,8 @@ let systemBootCheckTimer = null;
|
|
|
230
231
|
let systemBootCheckPromise = null;
|
|
231
232
|
let systemRestartDetected = false;
|
|
232
233
|
let chapterAnnotations = [];
|
|
234
|
+
let chapterAnnotationCounts = new Map();
|
|
235
|
+
let chapterAnnotationsLineIndex = null;
|
|
233
236
|
let workAuditRecords = [];
|
|
234
237
|
let workAuditNextPage = null;
|
|
235
238
|
const chapterBatchSelectedIds = new Set();
|
|
@@ -376,6 +379,13 @@ function canEditProse(work = state.work) {
|
|
|
376
379
|
return canWriteUiModule(work, "editor");
|
|
377
380
|
}
|
|
378
381
|
|
|
382
|
+
function canManageChapterAnnotation(annotation, work = state.work) {
|
|
383
|
+
if (!work) return false;
|
|
384
|
+
if (annotation.kind !== "todo") return canEditProse(work);
|
|
385
|
+
if (["admin", "owner"].includes(String(work.accessRole))) return true;
|
|
386
|
+
return annotation.createdByUserId === state.user?.userId && canWritePermissionModule(work, "todos");
|
|
387
|
+
}
|
|
388
|
+
|
|
379
389
|
function canReplaceProse(work = state.work) {
|
|
380
390
|
return WORK_PERMISSION_MODULES
|
|
381
391
|
.filter((item) => item.id !== "ai-settings")
|
|
@@ -1334,9 +1344,9 @@ function applyPanelLayout(persist = false) {
|
|
|
1334
1344
|
? (panelLayout.leftCollapsed ? "打开作品模块" : "关闭作品模块")
|
|
1335
1345
|
: (panelLayout.leftCollapsed ? "展开作品侧栏" : "收起作品侧栏"));
|
|
1336
1346
|
$("#mobile-module-tab").setAttribute("aria-expanded", String(!panelLayout.leftCollapsed));
|
|
1337
|
-
$("#ai-panel-toggle").textContent = panelLayout.aiCollapsed ? "‹" : "›";
|
|
1347
|
+
$("#ai-panel-toggle").textContent = aiConversationWorkspaceOpen ? "×" : (panelLayout.aiCollapsed ? "‹" : "›");
|
|
1338
1348
|
$("#ai-panel-toggle").setAttribute("aria-expanded", String(!panelLayout.aiCollapsed));
|
|
1339
|
-
$("#ai-panel-toggle").setAttribute("aria-label", panelLayout.aiCollapsed ? "展开创作助手" : "收起创作助手");
|
|
1349
|
+
$("#ai-panel-toggle").setAttribute("aria-label", aiConversationWorkspaceOpen ? "关闭创作助手全屏面板" : (panelLayout.aiCollapsed ? "展开创作助手" : "收起创作助手"));
|
|
1340
1350
|
syncMobileAiPanelSafeTop();
|
|
1341
1351
|
scheduleChapterLineNumbers();
|
|
1342
1352
|
if (persist) {
|
|
@@ -1346,7 +1356,12 @@ function applyPanelLayout(persist = false) {
|
|
|
1346
1356
|
|
|
1347
1357
|
function ensureAiPanelExpanded() {
|
|
1348
1358
|
if (!state.work || !canReadPermissionModule(state.work, "ai-chat")) return;
|
|
1349
|
-
|
|
1359
|
+
if (isMobileViewport()) {
|
|
1360
|
+
setAiConversationWorkspaceVisible(true);
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
panelLayout.aiCollapsed = false;
|
|
1364
|
+
applyPanelLayout(true);
|
|
1350
1365
|
}
|
|
1351
1366
|
|
|
1352
1367
|
function setupPanelResize(handle, side) {
|
|
@@ -1496,7 +1511,7 @@ function applyChapterEditorMode() {
|
|
|
1496
1511
|
$("#chapter-content").setAttribute("aria-readonly", String(viewOnly));
|
|
1497
1512
|
$("#chapter-edit-button").classList.toggle("hidden", permissionBlocked || !chapterEditorReadOnly || !state.chapter);
|
|
1498
1513
|
$("#chapter-delete-button").classList.toggle("hidden", permissionBlocked || chapterEditorReadOnly || !state.chapter);
|
|
1499
|
-
$("#chapter-annotations-button").classList.toggle("hidden", !state.chapter);
|
|
1514
|
+
$("#chapter-annotations-button").classList.toggle("hidden", !state.chapter || !canReadModule("comments"));
|
|
1500
1515
|
$("#chapter-reader-button").classList.toggle("hidden", !state.chapter || !canReadModule("editor"));
|
|
1501
1516
|
syncChapterSearchControls();
|
|
1502
1517
|
if (viewOnly) cancelChapterAutoSave();
|
|
@@ -1788,22 +1803,36 @@ function renderChapterLineNumbers({ targetLineIndex = null } = {}) {
|
|
|
1788
1803
|
);
|
|
1789
1804
|
const numbers = document.createDocumentFragment();
|
|
1790
1805
|
for (let index = lineWindow.start; index < lineWindow.end; index += 1) {
|
|
1791
|
-
const number = document.createElement("
|
|
1792
|
-
number.type = "button";
|
|
1806
|
+
const number = document.createElement("div");
|
|
1793
1807
|
number.className = "chapter-line-number";
|
|
1794
|
-
number.textContent = String(index + 1);
|
|
1795
1808
|
number.dataset.lineIndex = String(index);
|
|
1796
|
-
|
|
1797
|
-
|
|
1809
|
+
const lineSelect = document.createElement("button");
|
|
1810
|
+
lineSelect.type = "button";
|
|
1811
|
+
lineSelect.className = "chapter-line-number-select";
|
|
1812
|
+
lineSelect.textContent = String(index + 1);
|
|
1813
|
+
lineSelect.setAttribute("aria-label", `选择第 ${index + 1} 行`);
|
|
1814
|
+
lineSelect.tabIndex = -1;
|
|
1815
|
+
number.append(lineSelect);
|
|
1816
|
+
const annotationCount = chapterAnnotationCounts.get(index + 1) ?? 0;
|
|
1817
|
+
if (annotationCount > 0) {
|
|
1818
|
+
const bubble = document.createElement("button");
|
|
1819
|
+
bubble.type = "button";
|
|
1820
|
+
bubble.className = "chapter-line-annotation-count";
|
|
1821
|
+
bubble.dataset.lineIndex = String(index);
|
|
1822
|
+
bubble.dataset.lineAnnotationCount = String(annotationCount);
|
|
1823
|
+
bubble.textContent = String(annotationCount);
|
|
1824
|
+
bubble.setAttribute("aria-label", `查看第 ${index + 1} 行的 ${annotationCount} 条评论和待办`);
|
|
1825
|
+
number.append(bubble);
|
|
1826
|
+
}
|
|
1798
1827
|
const selected = chapterLineSelection && index >= chapterLineSelection.start && index <= chapterLineSelection.end;
|
|
1799
1828
|
if (selected) {
|
|
1800
1829
|
number.classList.add("is-line-selected");
|
|
1801
|
-
|
|
1830
|
+
lineSelect.setAttribute("aria-pressed", "true");
|
|
1802
1831
|
}
|
|
1803
1832
|
const bounds = getLineBounds(index);
|
|
1804
1833
|
number.style.top = `${bounds.top}px`;
|
|
1805
1834
|
number.style.height = `${bounds.bottom - bounds.top}px`;
|
|
1806
|
-
|
|
1835
|
+
lineSelect.style.paddingTop = `${numberTextOffset}px`;
|
|
1807
1836
|
numbers.append(number);
|
|
1808
1837
|
}
|
|
1809
1838
|
inner.replaceChildren(numbers);
|
|
@@ -1879,7 +1908,7 @@ function paintChapterLineSelection(anchor, focus) {
|
|
|
1879
1908
|
$("#chapter-line-numbers-inner").querySelectorAll(".chapter-line-number").forEach((row) => {
|
|
1880
1909
|
const selected = Number(row.dataset.lineIndex) >= start && Number(row.dataset.lineIndex) <= end;
|
|
1881
1910
|
row.classList.toggle("is-line-selected", selected);
|
|
1882
|
-
row.setAttribute("aria-pressed", String(selected));
|
|
1911
|
+
row.querySelector(".chapter-line-number-select")?.setAttribute("aria-pressed", String(selected));
|
|
1883
1912
|
});
|
|
1884
1913
|
}
|
|
1885
1914
|
|
|
@@ -2012,6 +2041,7 @@ function createAiChatTabState(input = {}) {
|
|
|
2012
2041
|
taskType: input.taskType ?? "chat",
|
|
2013
2042
|
contextScope: input.contextScope ?? { type: "none" },
|
|
2014
2043
|
roleplayCharacter: input.roleplayCharacter ?? null,
|
|
2044
|
+
roleplayUserCharacter: input.roleplayUserCharacter ?? null,
|
|
2015
2045
|
citations: input.citations ?? [],
|
|
2016
2046
|
references: input.references ?? [],
|
|
2017
2047
|
composer: input.composer ?? { text: "", citations: [], references: [] },
|
|
@@ -2039,6 +2069,7 @@ function persistActiveAiChatTab() {
|
|
|
2039
2069
|
tab.taskType = state.aiTaskType;
|
|
2040
2070
|
tab.contextScope = { ...state.aiContextScope };
|
|
2041
2071
|
tab.roleplayCharacter = state.aiRoleplayCharacter ? { ...state.aiRoleplayCharacter } : null;
|
|
2072
|
+
tab.roleplayUserCharacter = state.aiRoleplayUserCharacter ? { ...state.aiRoleplayUserCharacter } : null;
|
|
2042
2073
|
setAiChatTabComposerSnapshot(tab, captureAiPromptComposer());
|
|
2043
2074
|
tab.contextUsage = latestAiContextUsage;
|
|
2044
2075
|
tab.contextWarning = !$("#ai-context-warning").classList.contains("hidden");
|
|
@@ -2071,9 +2102,10 @@ function applyAiChatTabState(tab) {
|
|
|
2071
2102
|
$("#ai-conversation-title").textContent = tab.title || "新对话";
|
|
2072
2103
|
applyAiConversationTaskType(tab.taskType);
|
|
2073
2104
|
applyAiConversationContextScope(tab.contextScope);
|
|
2074
|
-
applyAiRoleplayCharacter(tab.roleplayCharacter);
|
|
2105
|
+
applyAiRoleplayCharacter(tab.roleplayCharacter, tab.roleplayUserCharacter);
|
|
2075
2106
|
const selectedModelId = tab.modelId ?? tab.selectedModelId;
|
|
2076
2107
|
if (selectedModelId && state.models.some((model) => model.id === selectedModelId)) $("#ai-model").value = selectedModelId;
|
|
2108
|
+
syncAiModelPicker();
|
|
2077
2109
|
setAiPromptText(tab.composer.text);
|
|
2078
2110
|
renderAiCitations();
|
|
2079
2111
|
renderAiReferences();
|
|
@@ -2187,20 +2219,21 @@ function setAiConversationSwitcherVisible(visible) {
|
|
|
2187
2219
|
}
|
|
2188
2220
|
|
|
2189
2221
|
function setAiConversationWorkspaceVisible(visible) {
|
|
2222
|
+
const mobileWorkspace = isMobileViewport();
|
|
2190
2223
|
aiConversationWorkspaceOpen = Boolean(visible);
|
|
2191
|
-
|
|
2192
|
-
panelLayout.aiCollapsed = false;
|
|
2193
|
-
$("#app").classList.remove("ai-panel-collapsed");
|
|
2194
|
-
}
|
|
2224
|
+
panelLayout.aiCollapsed = aiConversationWorkspaceOpen ? false : mobileWorkspace;
|
|
2195
2225
|
$("#app").classList.toggle("ai-workspace-mode", aiConversationWorkspaceOpen);
|
|
2196
2226
|
$(".ai-panel").classList.toggle("is-conversation-workspace", aiConversationWorkspaceOpen);
|
|
2197
2227
|
$("#ai-assistant-entry").classList.toggle("active", aiConversationWorkspaceOpen);
|
|
2198
2228
|
$("#ai-assistant-entry").setAttribute("aria-expanded", String(aiConversationWorkspaceOpen));
|
|
2229
|
+
applyPanelLayout(true);
|
|
2199
2230
|
$("#ai-chat-tabs").classList.add("hidden");
|
|
2200
2231
|
$("#ai-workspace-close").classList.toggle("hidden", !aiConversationWorkspaceOpen);
|
|
2201
2232
|
$("#ai-panel-resize").setAttribute("aria-hidden", String(aiConversationWorkspaceOpen));
|
|
2202
2233
|
setAiConversationSwitcherVisible(false);
|
|
2203
2234
|
renderAiChatTabs();
|
|
2235
|
+
$("#ai-panel-toggle").textContent = aiConversationWorkspaceOpen ? "×" : (panelLayout.aiCollapsed ? "‹" : "›");
|
|
2236
|
+
$("#ai-panel-toggle").setAttribute("aria-label", aiConversationWorkspaceOpen ? "关闭创作助手全屏面板" : (panelLayout.aiCollapsed ? "展开创作助手" : "收起创作助手"));
|
|
2204
2237
|
if (aiConversationWorkspaceOpen) {
|
|
2205
2238
|
window.requestAnimationFrame(() => $("#ai-prompt").focus({ preventScroll: true }));
|
|
2206
2239
|
}
|
|
@@ -2311,7 +2344,7 @@ function resetAiChatTabs() {
|
|
|
2311
2344
|
panels.replaceChildren();
|
|
2312
2345
|
aiChatTabManager.reset();
|
|
2313
2346
|
const tab = createAiChatTabState();
|
|
2314
|
-
resetAiFeed(tab.feed, tab.roleplayCharacter);
|
|
2347
|
+
resetAiFeed(tab.feed, tab.roleplayCharacter, tab.roleplayUserCharacter);
|
|
2315
2348
|
activateAiChatTab(tab.id, { persistCurrent: false, force: true });
|
|
2316
2349
|
}
|
|
2317
2350
|
|
|
@@ -2356,13 +2389,18 @@ function updateMessageCreatedAt(message, createdAt) {
|
|
|
2356
2389
|
}
|
|
2357
2390
|
}
|
|
2358
2391
|
|
|
2359
|
-
function resetAiFeed(
|
|
2392
|
+
function resetAiFeed(
|
|
2393
|
+
feed = $("#ai-feed"),
|
|
2394
|
+
roleplayCharacter = state.aiRoleplayCharacter,
|
|
2395
|
+
roleplayUserCharacter = state.aiRoleplayUserCharacter
|
|
2396
|
+
) {
|
|
2360
2397
|
const tab = aiChatTabManager.get(feed?.dataset.aiTabId);
|
|
2361
2398
|
if (tab) tab.lastMessageAt = null;
|
|
2362
2399
|
if (isActiveAiChatTab(tab)) state.aiLastMessageAt = null;
|
|
2363
2400
|
const roleplayName = roleplayCharacter?.name;
|
|
2401
|
+
const roleplayUserName = roleplayUserCharacter?.name;
|
|
2364
2402
|
feed.innerHTML = roleplayName
|
|
2365
|
-
? `<div class="assistant-message"><span class="message-heading"><span>${esc(roleplayName)}</span></span><div class="message-body"><p>正在扮演 ${esc(roleplayName)}
|
|
2403
|
+
? `<div class="assistant-message"><span class="message-heading"><span>${esc(roleplayName)}</span></span><div class="message-body"><p>正在扮演 ${esc(roleplayName)}。${roleplayUserName ? `你将以 ${esc(roleplayUserName)} 的身份与我互动。` : "我只能通过角色卡和与自己有关的记忆回答。"}</p></div></div>`
|
|
2366
2404
|
: '<div class="assistant-message"><span class="message-heading"><span>助手</span></span><div class="message-body"><p>选择章节和模型后,可以问答、续写或校对。所有引用都基于已保存正文。</p></div></div>';
|
|
2367
2405
|
}
|
|
2368
2406
|
|
|
@@ -2371,6 +2409,10 @@ function aiAssistantLabel(suffix = "", roleplayCharacter = state.aiRoleplayChara
|
|
|
2371
2409
|
return suffix ? `${name} · ${suffix}` : name;
|
|
2372
2410
|
}
|
|
2373
2411
|
|
|
2412
|
+
function aiUserLabel(roleplayUserCharacter = state.aiRoleplayUserCharacter) {
|
|
2413
|
+
return roleplayUserCharacter?.name || "作者";
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2374
2416
|
function createAiContextCompactionDivider({ kind = "conversation", ariaLabel = "当前上下文已压缩", title = "" } = {}) {
|
|
2375
2417
|
const divider = document.createElement("div");
|
|
2376
2418
|
divider.className = "ai-context-compaction-divider";
|
|
@@ -2411,16 +2453,18 @@ function renderMessageCardActions(message) {
|
|
|
2411
2453
|
message.append(actions);
|
|
2412
2454
|
}
|
|
2413
2455
|
actions.replaceChildren();
|
|
2414
|
-
|
|
2456
|
+
const hasCopyValue = Object.hasOwn(message.dataset, "rawMarkdown") || Object.hasOwn(message.dataset, "copyText");
|
|
2457
|
+
if (hasCopyValue) {
|
|
2415
2458
|
const copy = document.createElement("button");
|
|
2416
2459
|
copy.type = "button";
|
|
2417
2460
|
copy.className = "message-copy-button";
|
|
2418
|
-
|
|
2461
|
+
const isUserMessage = message.classList.contains("user-message");
|
|
2462
|
+
copy.setAttribute("aria-label", isUserMessage ? "复制用户指令" : "复制 AI 回复");
|
|
2419
2463
|
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
2464
|
const copyLabel = copy.querySelector("span");
|
|
2421
2465
|
copy.addEventListener("click", async () => {
|
|
2422
2466
|
try {
|
|
2423
|
-
await copyAiRawMarkdown(message.dataset.rawMarkdown);
|
|
2467
|
+
await copyAiRawMarkdown(message.dataset.rawMarkdown ?? message.dataset.copyText ?? "");
|
|
2424
2468
|
copyLabel.textContent = "已复制";
|
|
2425
2469
|
window.setTimeout(() => { copyLabel.textContent = "复制"; }, 1200);
|
|
2426
2470
|
} catch (error) {
|
|
@@ -2470,6 +2514,11 @@ function attachAssistantCopyAction(message, rawMarkdown) {
|
|
|
2470
2514
|
renderMessageCardActions(message);
|
|
2471
2515
|
}
|
|
2472
2516
|
|
|
2517
|
+
function attachUserCopyAction(message, text) {
|
|
2518
|
+
message.dataset.copyText = String(text ?? "");
|
|
2519
|
+
renderMessageCardActions(message);
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2473
2522
|
function attachMessageIdentity(message, messageId) {
|
|
2474
2523
|
if (!messageId) return;
|
|
2475
2524
|
message.dataset.messageId = messageId;
|
|
@@ -2865,6 +2914,7 @@ function aiConversationHistoryMeta(conversation) {
|
|
|
2865
2914
|
aiConversationContextScopeLabel(conversation.contextScope)
|
|
2866
2915
|
];
|
|
2867
2916
|
if (conversation.roleplayCharacter?.name) parts.push(`扮演 ${conversation.roleplayCharacter.name}`);
|
|
2917
|
+
if (conversation.roleplayUserCharacter?.name) parts.push(`用户扮演 ${conversation.roleplayUserCharacter.name}`);
|
|
2868
2918
|
parts.push(formatDateTime(conversation.updatedAt));
|
|
2869
2919
|
return parts.join(" · ");
|
|
2870
2920
|
}
|
|
@@ -3081,12 +3131,13 @@ function applyConversationToAiChatTab(tab, conversation) {
|
|
|
3081
3131
|
tab.taskType = conversation.taskType ?? "chat";
|
|
3082
3132
|
tab.contextScope = conversation.contextScope ?? { type: "none" };
|
|
3083
3133
|
tab.roleplayCharacter = conversation.roleplayCharacter ?? null;
|
|
3134
|
+
tab.roleplayUserCharacter = conversation.roleplayUserCharacter ?? null;
|
|
3084
3135
|
tab.citations = [];
|
|
3085
3136
|
tab.references = [];
|
|
3086
3137
|
tab.composer = { text: "", citations: [], references: [] };
|
|
3087
3138
|
tab.contextUsage = null;
|
|
3088
3139
|
tab.contextWarning = conversation.contextWarningPending === true;
|
|
3089
|
-
resetAiFeed(tab.feed, tab.roleplayCharacter);
|
|
3140
|
+
resetAiFeed(tab.feed, tab.roleplayCharacter, tab.roleplayUserCharacter);
|
|
3090
3141
|
for (const message of conversation.messages ?? []) {
|
|
3091
3142
|
appendMessage(message.role, message.content, message.citations, message.createdAt, message.metadata, message.id, { tab });
|
|
3092
3143
|
}
|
|
@@ -3161,10 +3212,65 @@ function renderAiRoleplayCharacterSelect() {
|
|
|
3161
3212
|
? aiConversationOptionLockedMessage
|
|
3162
3213
|
: "为当前对话选择角色卡;角色扮演时 Agent 只能查询与该角色自身有关的记忆"
|
|
3163
3214
|
: "当前账户没有角色模块读取权限";
|
|
3215
|
+
renderAiRoleplayUserCharacterSelect();
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
function renderAiRoleplayUserCharacterSelect() {
|
|
3219
|
+
const select = $("#ai-roleplay-user-character");
|
|
3220
|
+
const selectedId = String(state.aiRoleplayUserCharacter?.id ?? "");
|
|
3221
|
+
const aiCharacterId = String(state.aiRoleplayCharacter?.id ?? "");
|
|
3222
|
+
const availableCharacters = state.characters.filter((character) => (
|
|
3223
|
+
!character.mergedIntoCharacterId && String(character.id) !== aiCharacterId
|
|
3224
|
+
));
|
|
3225
|
+
const options = [{ id: "", name: "选择我的角色(可选)" }, ...availableCharacters.map((character) => ({
|
|
3226
|
+
id: String(character.id),
|
|
3227
|
+
name: `${String(character.name)}${character.isDead ? "(已死亡)" : ""}`
|
|
3228
|
+
}))];
|
|
3229
|
+
if (selectedId && !options.some((option) => option.id === selectedId)) {
|
|
3230
|
+
options.push({ id: selectedId, name: String(state.aiRoleplayUserCharacter.name) });
|
|
3231
|
+
}
|
|
3232
|
+
select.replaceChildren(...options.map((option) => {
|
|
3233
|
+
const element = document.createElement("option");
|
|
3234
|
+
element.value = option.id;
|
|
3235
|
+
element.textContent = option.name;
|
|
3236
|
+
return element;
|
|
3237
|
+
}));
|
|
3238
|
+
select.value = selectedId;
|
|
3239
|
+
const canSelectCharacter = Boolean(state.work)
|
|
3240
|
+
&& canReadModule("characters")
|
|
3241
|
+
&& canWritePermissionModule(state.work, "ai-chat");
|
|
3242
|
+
select.disabled = aiInteractionBusy() || !canSelectCharacter || !state.aiRoleplayCharacter;
|
|
3243
|
+
select.title = !canSelectCharacter
|
|
3244
|
+
? "当前账户没有角色模块读取权限"
|
|
3245
|
+
: !state.aiRoleplayCharacter
|
|
3246
|
+
? "请先选择 AI 扮演的角色"
|
|
3247
|
+
: state.aiPromptSent
|
|
3248
|
+
? aiConversationOptionLockedMessage
|
|
3249
|
+
: "选择后,AI 会将每条用户消息视为该角色的发言或行动";
|
|
3164
3250
|
}
|
|
3165
3251
|
|
|
3166
3252
|
const aiConversationOptionLockedMessage = "会话选项在会话开始后不支持修改,若需要修改,请新建会话";
|
|
3167
3253
|
|
|
3254
|
+
function selectedAiModelLabel() {
|
|
3255
|
+
return $("#ai-model").selectedOptions[0]?.textContent?.trim() || "尚未选择模型";
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
function syncAiModelPicker() {
|
|
3259
|
+
const select = $("#ai-model");
|
|
3260
|
+
const button = $("#ai-model-picker");
|
|
3261
|
+
const interactionBusy = aiInteractionBusy();
|
|
3262
|
+
const selectedLabel = selectedAiModelLabel();
|
|
3263
|
+
const label = state.aiPromptSent
|
|
3264
|
+
? `当前模型:${selectedLabel}。${aiConversationOptionLockedMessage}`
|
|
3265
|
+
: `选择实际使用模型:${selectedLabel}`;
|
|
3266
|
+
select.disabled = interactionBusy;
|
|
3267
|
+
select.title = state.aiPromptSent ? aiConversationOptionLockedMessage : "";
|
|
3268
|
+
button.disabled = interactionBusy;
|
|
3269
|
+
button.title = label;
|
|
3270
|
+
button.setAttribute("aria-label", label);
|
|
3271
|
+
if (interactionBusy) setAiModelPickerVisible(false);
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3168
3274
|
function notifyAiConversationOptionLocked(select) {
|
|
3169
3275
|
if (!state.aiPromptSent) return false;
|
|
3170
3276
|
const now = Date.now();
|
|
@@ -3188,19 +3294,21 @@ function blockLockedAiConversationOptionKeydown(event) {
|
|
|
3188
3294
|
|
|
3189
3295
|
function syncAiTaskOptions() {
|
|
3190
3296
|
const roleplaySelected = $("#ai-task").value === "roleplay";
|
|
3297
|
+
const relationshipRoleplaySelectable = roleplaySelected && Boolean(state.aiRoleplayCharacter);
|
|
3191
3298
|
const interactionBusy = aiInteractionBusy();
|
|
3192
3299
|
$("#ai-scope").classList.toggle("hidden", roleplaySelected);
|
|
3193
3300
|
$("#ai-scope").setAttribute("aria-hidden", String(roleplaySelected));
|
|
3194
3301
|
$("#ai-roleplay-character").classList.toggle("hidden", !roleplaySelected);
|
|
3195
3302
|
$("#ai-roleplay-character").setAttribute("aria-hidden", String(!roleplaySelected));
|
|
3303
|
+
$("#ai-roleplay-user-character").classList.toggle("hidden", !relationshipRoleplaySelectable);
|
|
3304
|
+
$("#ai-roleplay-user-character").setAttribute("aria-hidden", String(!relationshipRoleplaySelectable));
|
|
3196
3305
|
$("#ai-task").disabled = interactionBusy;
|
|
3197
3306
|
$("#ai-task").title = state.aiPromptSent ? aiConversationOptionLockedMessage : "";
|
|
3198
3307
|
$("#ai-scope").disabled = interactionBusy || roleplaySelected;
|
|
3199
3308
|
$("#ai-scope").title = state.aiPromptSent
|
|
3200
3309
|
? aiConversationOptionLockedMessage
|
|
3201
3310
|
: roleplaySelected ? "角色扮演模式只使用角色自身的记忆" : "";
|
|
3202
|
-
|
|
3203
|
-
$("#ai-model").title = state.aiPromptSent ? aiConversationOptionLockedMessage : "";
|
|
3311
|
+
syncAiModelPicker();
|
|
3204
3312
|
}
|
|
3205
3313
|
|
|
3206
3314
|
function applyAiConversationTaskType(taskType) {
|
|
@@ -3226,15 +3334,19 @@ function applyAiConversationContextScope(scope) {
|
|
|
3226
3334
|
syncAiTaskOptions();
|
|
3227
3335
|
}
|
|
3228
3336
|
|
|
3229
|
-
function applyAiRoleplayCharacter(character) {
|
|
3337
|
+
function applyAiRoleplayCharacter(character, userCharacter = null) {
|
|
3230
3338
|
state.aiRoleplayCharacter = character?.id ? character : null;
|
|
3339
|
+
state.aiRoleplayUserCharacter = state.aiRoleplayCharacter && userCharacter?.id ? userCharacter : null;
|
|
3231
3340
|
const tab = activeAiChatTab();
|
|
3232
3341
|
if (tab) tab.roleplayCharacter = state.aiRoleplayCharacter ? { ...state.aiRoleplayCharacter } : null;
|
|
3342
|
+
if (tab) tab.roleplayUserCharacter = state.aiRoleplayUserCharacter ? { ...state.aiRoleplayUserCharacter } : null;
|
|
3233
3343
|
const active = Boolean(state.aiRoleplayCharacter);
|
|
3234
3344
|
if (active) $("#ai-scope").value = "none";
|
|
3235
3345
|
$(".ai-panel").classList.toggle("is-roleplaying", active);
|
|
3236
3346
|
$("#ai-prompt").dataset.placeholder = active
|
|
3237
|
-
?
|
|
3347
|
+
? state.aiRoleplayUserCharacter
|
|
3348
|
+
? `以 ${String(state.aiRoleplayUserCharacter.name)} 的身份与 ${String(state.aiRoleplayCharacter.name)} 对话……`
|
|
3349
|
+
: `与 ${String(state.aiRoleplayCharacter.name)} 角色开始对话……`
|
|
3238
3350
|
: "告诉 AI 你想讨论或修改什么……";
|
|
3239
3351
|
renderAiRoleplayCharacterSelect();
|
|
3240
3352
|
syncAiTaskOptions();
|
|
@@ -3245,22 +3357,27 @@ function refreshAiMessageRoleLabels() {
|
|
|
3245
3357
|
$("#ai-feed").querySelectorAll(".assistant-message .message-heading > span").forEach((label) => {
|
|
3246
3358
|
label.textContent = aiAssistantLabel();
|
|
3247
3359
|
});
|
|
3360
|
+
$("#ai-feed").querySelectorAll(".user-message .message-heading > span").forEach((label) => {
|
|
3361
|
+
label.textContent = aiUserLabel();
|
|
3362
|
+
});
|
|
3248
3363
|
}
|
|
3249
3364
|
|
|
3250
|
-
async function updateAiRoleplayCharacter(characterId) {
|
|
3365
|
+
async function updateAiRoleplayCharacter(characterId, userCharacterId = null) {
|
|
3251
3366
|
const conversationId = await ensureAiConversation();
|
|
3252
3367
|
const conversation = await api(`/api/ai-conversations/${conversationId}/roleplay`, {
|
|
3253
3368
|
method: "PATCH",
|
|
3254
|
-
body: { characterId: characterId || null }
|
|
3369
|
+
body: { characterId: characterId || null, userCharacterId: userCharacterId || null }
|
|
3255
3370
|
});
|
|
3256
3371
|
upsertAiConversationSummary(conversation);
|
|
3257
3372
|
applyAiConversationTaskType(conversation.taskType);
|
|
3258
|
-
applyAiRoleplayCharacter(conversation.roleplayCharacter);
|
|
3373
|
+
applyAiRoleplayCharacter(conversation.roleplayCharacter, conversation.roleplayUserCharacter);
|
|
3259
3374
|
resetAiContextMeter();
|
|
3260
3375
|
if ($("#ai-feed").querySelector("[data-message-id]")) refreshAiMessageRoleLabels();
|
|
3261
3376
|
else resetAiFeed();
|
|
3262
3377
|
toast(conversation.roleplayCharacter
|
|
3263
|
-
?
|
|
3378
|
+
? conversation.roleplayUserCharacter
|
|
3379
|
+
? `已进入 ${conversation.roleplayCharacter.name} 与 ${conversation.roleplayUserCharacter.name} 的关系扮演模式`
|
|
3380
|
+
: `已进入 ${conversation.roleplayCharacter.name} 的角色扮演模式`
|
|
3264
3381
|
: "已退出角色扮演模式");
|
|
3265
3382
|
}
|
|
3266
3383
|
|
|
@@ -3278,7 +3395,7 @@ async function persistAiConversationTaskType(taskType) {
|
|
|
3278
3395
|
});
|
|
3279
3396
|
upsertAiConversationSummary(conversation);
|
|
3280
3397
|
applyAiConversationTaskType(conversation.taskType);
|
|
3281
|
-
applyAiRoleplayCharacter(conversation.roleplayCharacter);
|
|
3398
|
+
applyAiRoleplayCharacter(conversation.roleplayCharacter, conversation.roleplayUserCharacter);
|
|
3282
3399
|
return conversation;
|
|
3283
3400
|
}
|
|
3284
3401
|
|
|
@@ -3318,9 +3435,10 @@ async function prepareAiRequestConversation(requestHolder, taskType, scope) {
|
|
|
3318
3435
|
upsertAiConversationSummary(taskConversation);
|
|
3319
3436
|
tab.taskType = taskConversation.taskType;
|
|
3320
3437
|
tab.roleplayCharacter = taskConversation.roleplayCharacter ?? null;
|
|
3438
|
+
tab.roleplayUserCharacter = taskConversation.roleplayUserCharacter ?? null;
|
|
3321
3439
|
if (isActiveAiChatTab(tab)) {
|
|
3322
3440
|
applyAiConversationTaskType(tab.taskType);
|
|
3323
|
-
applyAiRoleplayCharacter(tab.roleplayCharacter);
|
|
3441
|
+
applyAiRoleplayCharacter(tab.roleplayCharacter, tab.roleplayUserCharacter);
|
|
3324
3442
|
}
|
|
3325
3443
|
|
|
3326
3444
|
const scopedConversation = await api(`/api/ai-conversations/${encodeURIComponent(request.conversationId)}/context-scope`, {
|
|
@@ -3576,7 +3694,8 @@ function addSelectedLinesAsCitation() {
|
|
|
3576
3694
|
}
|
|
3577
3695
|
|
|
3578
3696
|
async function createSelectedLineAnnotation(kind) {
|
|
3579
|
-
|
|
3697
|
+
const permissionModule = kind === "todo" ? "todos" : "comments";
|
|
3698
|
+
if (!state.chapter || !chapterLineSelection || !canWritePermissionModule(state.work, permissionModule)) return;
|
|
3580
3699
|
const selection = selectedChapterLinePayload(chapterLineSelection.start, chapterLineSelection.end);
|
|
3581
3700
|
closeLineCitationMenu();
|
|
3582
3701
|
const note = await inputToast(kind === "todo" ? "描述需要后续处理的事项" : `评论第 ${selection.safeStart + 1} 行正文`, {
|
|
@@ -3592,7 +3711,8 @@ async function createSelectedLineAnnotation(kind) {
|
|
|
3592
3711
|
body: { kind, startLine: selection.safeStart + 1, endLine: selection.safeEnd + 1, note }
|
|
3593
3712
|
});
|
|
3594
3713
|
toast(kind === "todo" ? "正文待办已添加" : "正文评论已添加");
|
|
3595
|
-
await
|
|
3714
|
+
await loadChapterAnnotationCounts();
|
|
3715
|
+
await openChapterAnnotationsDialog(selection.safeStart);
|
|
3596
3716
|
} catch (error) {
|
|
3597
3717
|
toast(error.message, "error");
|
|
3598
3718
|
}
|
|
@@ -3620,7 +3740,7 @@ function chapterAnnotationCard(annotation, { showSource = false } = {}) {
|
|
|
3620
3740
|
<blockquote>${esc(annotation.quote || "空白行")}</blockquote>
|
|
3621
3741
|
<p data-annotation-content>${esc(annotation.note)}</p>
|
|
3622
3742
|
<small>${esc(annotation.actor)} · ${esc(formatDateTime(annotation.updatedAt))} · v${Number(annotation.versionNo)}</small>
|
|
3623
|
-
<footer><button type="button" data-annotation-locate>定位原文</button>${
|
|
3743
|
+
<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
3744
|
</article>`;
|
|
3625
3745
|
}
|
|
3626
3746
|
|
|
@@ -3680,9 +3800,12 @@ function renderChapterAnnotations() {
|
|
|
3680
3800
|
const host = $("#chapter-annotations-list");
|
|
3681
3801
|
host.innerHTML = chapterAnnotations.length
|
|
3682
3802
|
? chapterAnnotations.map((annotation) => chapterAnnotationCard(annotation)).join("")
|
|
3683
|
-
:
|
|
3803
|
+
: `<p class="entity-history-empty">${chapterAnnotationsLineIndex === null ? "本章还没有评论。在正文行上点击右键即可添加。" : `第 ${chapterAnnotationsLineIndex + 1} 行还没有评论或待办。`}</p>`;
|
|
3684
3804
|
bindChapterAnnotationCards(host, chapterAnnotations, {
|
|
3685
|
-
refresh:
|
|
3805
|
+
refresh: async () => {
|
|
3806
|
+
await loadChapterAnnotationCounts();
|
|
3807
|
+
await loadChapterAnnotations(chapterAnnotationsLineIndex);
|
|
3808
|
+
},
|
|
3686
3809
|
locate: (annotation) => {
|
|
3687
3810
|
$("#chapter-annotations-dialog").close();
|
|
3688
3811
|
paintChapterLineSelection(annotation.startLine - 1, annotation.endLine - 1);
|
|
@@ -3692,19 +3815,43 @@ function renderChapterAnnotations() {
|
|
|
3692
3815
|
});
|
|
3693
3816
|
}
|
|
3694
3817
|
|
|
3695
|
-
async function
|
|
3818
|
+
async function loadChapterAnnotationCounts(chapterId = state.chapter?.id) {
|
|
3819
|
+
if (!chapterId || !canReadModule("comments") && !canReadModule("todos")) {
|
|
3820
|
+
chapterAnnotationCounts = new Map();
|
|
3821
|
+
scheduleChapterLineNumbers();
|
|
3822
|
+
return;
|
|
3823
|
+
}
|
|
3824
|
+
const counts = await api(`/api/chapters/${encodeURIComponent(chapterId)}/annotation-counts`);
|
|
3825
|
+
if (String(state.chapter?.id ?? "") !== String(chapterId)) return;
|
|
3826
|
+
chapterAnnotationCounts = new Map(
|
|
3827
|
+
(Array.isArray(counts) ? counts : [])
|
|
3828
|
+
.map((item) => [Number(item.line), Number(item.count)])
|
|
3829
|
+
.filter(([line, count]) => Number.isInteger(line) && line > 0 && Number.isInteger(count) && count > 0)
|
|
3830
|
+
);
|
|
3831
|
+
scheduleChapterLineNumbers();
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
async function loadChapterAnnotations(lineIndex = chapterAnnotationsLineIndex) {
|
|
3696
3835
|
if (!state.chapter) return;
|
|
3697
|
-
|
|
3836
|
+
const chapterId = state.chapter.id;
|
|
3837
|
+
const line = Number.isInteger(lineIndex) && lineIndex >= 0 ? lineIndex + 1 : null;
|
|
3838
|
+
const query = line === null ? "" : `?line=${encodeURIComponent(line)}`;
|
|
3839
|
+
const annotations = await api(`/api/chapters/${encodeURIComponent(chapterId)}/annotations${query}`);
|
|
3840
|
+
if (String(state.chapter?.id ?? "") !== String(chapterId)) return;
|
|
3841
|
+
chapterAnnotationsLineIndex = line === null ? null : line - 1;
|
|
3842
|
+
chapterAnnotations = annotations;
|
|
3698
3843
|
renderChapterAnnotations();
|
|
3699
3844
|
}
|
|
3700
3845
|
|
|
3701
|
-
async function openChapterAnnotationsDialog() {
|
|
3846
|
+
async function openChapterAnnotationsDialog(lineIndex = null) {
|
|
3702
3847
|
if (!state.chapter) return;
|
|
3703
|
-
|
|
3848
|
+
chapterAnnotationsLineIndex = Number.isInteger(lineIndex) && lineIndex >= 0 ? lineIndex : null;
|
|
3849
|
+
const lineLabel = chapterAnnotationsLineIndex === null ? "" : `第 ${chapterAnnotationsLineIndex + 1} 行的`;
|
|
3850
|
+
$("#chapter-annotations-meta").textContent = `《${state.chapter.title}》${lineLabel}正文评论与待办`;
|
|
3704
3851
|
$("#chapter-annotations-list").innerHTML = '<p class="entity-history-empty">正在加载评论…</p>';
|
|
3705
3852
|
if (!$("#chapter-annotations-dialog").open) $("#chapter-annotations-dialog").showModal();
|
|
3706
3853
|
try {
|
|
3707
|
-
await loadChapterAnnotations();
|
|
3854
|
+
await loadChapterAnnotations(chapterAnnotationsLineIndex);
|
|
3708
3855
|
} catch (error) {
|
|
3709
3856
|
$("#chapter-annotations-dialog").close();
|
|
3710
3857
|
toast(error.message, "error");
|
|
@@ -3722,8 +3869,8 @@ function showLineCitationMenu(event, lineIndex) {
|
|
|
3722
3869
|
selectChapterLines(lineIndex, lineIndex);
|
|
3723
3870
|
}
|
|
3724
3871
|
const menu = $("#line-citation-menu");
|
|
3725
|
-
$("#add-line-annotation").classList.toggle("hidden", !
|
|
3726
|
-
$("#add-line-todo").classList.toggle("hidden", !
|
|
3872
|
+
$("#add-line-annotation").classList.toggle("hidden", !canWritePermissionModule(state.work, "comments"));
|
|
3873
|
+
$("#add-line-todo").classList.toggle("hidden", !canWritePermissionModule(state.work, "todos"));
|
|
3727
3874
|
const { start, end } = chapterLineSelection;
|
|
3728
3875
|
$("#line-citation-label").textContent = start === end ? `第 ${start + 1} 行` : `第 ${start + 1}-${end + 1} 行`;
|
|
3729
3876
|
menu.classList.remove("hidden");
|
|
@@ -3736,7 +3883,7 @@ function clearChapterLineSelection() {
|
|
|
3736
3883
|
chapterLineSelection = null;
|
|
3737
3884
|
$("#chapter-line-numbers-inner")?.querySelectorAll(".is-line-selected").forEach((row) => {
|
|
3738
3885
|
row.classList.remove("is-line-selected");
|
|
3739
|
-
row.setAttribute("aria-pressed", "false");
|
|
3886
|
+
row.querySelector(".chapter-line-number-select")?.setAttribute("aria-pressed", "false");
|
|
3740
3887
|
});
|
|
3741
3888
|
}
|
|
3742
3889
|
|
|
@@ -5860,7 +6007,7 @@ function renderMemberPermissionGrid(value) {
|
|
|
5860
6007
|
<select data-member-permission="${esc(item.id)}" aria-label="${esc(item.label)}权限">
|
|
5861
6008
|
<option value="none" ${permissions[item.id] === "none" ? "selected" : ""}>无权限</option>
|
|
5862
6009
|
<option value="read" ${permissions[item.id] === "read" ? "selected" : ""}>只读</option>
|
|
5863
|
-
<option value="write" ${permissions[item.id] === "write" ? "selected" : ""}
|
|
6010
|
+
<option value="write" ${permissions[item.id] === "write" ? "selected" : ""}>${["comments", "todos"].includes(item.id) ? "可添加" : "可编辑"}</option>
|
|
5864
6011
|
</select>
|
|
5865
6012
|
</label>`).join("");
|
|
5866
6013
|
}
|
|
@@ -6458,6 +6605,7 @@ function resetWorkScopedUiCaches() {
|
|
|
6458
6605
|
state.characters = [];
|
|
6459
6606
|
state.settings = [];
|
|
6460
6607
|
state.races = [];
|
|
6608
|
+
state.organizations = [];
|
|
6461
6609
|
characterListPage = 1;
|
|
6462
6610
|
draftTypeFilter = "all";
|
|
6463
6611
|
draftBindingFilters = [];
|
|
@@ -6488,6 +6636,8 @@ function resetWorkScopedUiCaches() {
|
|
|
6488
6636
|
state.aiConversations = [];
|
|
6489
6637
|
resetAiChatTabs();
|
|
6490
6638
|
$("#ai-model").innerHTML = '<option value="">使用创作助手时加载模型</option>';
|
|
6639
|
+
syncAiModelPicker();
|
|
6640
|
+
setAiModelPickerVisible(false);
|
|
6491
6641
|
renderAiConversationHistory();
|
|
6492
6642
|
}
|
|
6493
6643
|
|
|
@@ -7174,8 +7324,12 @@ async function selectChapter(chapterId, { editMode = false } = {}) {
|
|
|
7174
7324
|
const normalizedContent = normalizeParagraphSpacing(state.chapter.content);
|
|
7175
7325
|
const spacingChanged = normalizedContent !== state.chapter.content;
|
|
7176
7326
|
$("#chapter-content").value = normalizedContent;
|
|
7327
|
+
chapterAnnotationCounts = new Map();
|
|
7177
7328
|
clearChapterLineSelection();
|
|
7178
7329
|
scheduleChapterLineNumbers();
|
|
7330
|
+
void loadChapterAnnotationCounts(state.chapter.id).catch((error) => {
|
|
7331
|
+
if (String(state.chapter?.id ?? "") === String(chapterId)) toast("正文评论数量读取失败,请稍后重试", "error");
|
|
7332
|
+
});
|
|
7179
7333
|
dismissChapterInsightToast();
|
|
7180
7334
|
updateChapterStats();
|
|
7181
7335
|
if (!canEditProse()) setSaveState("正文只读");
|
|
@@ -7711,7 +7865,7 @@ const moduleMeta = {
|
|
|
7711
7865
|
timeline: ["剧情脉络", "大事件时间轴", "候选事件经作者确认后,才进入正式时间线。", "新建事件"],
|
|
7712
7866
|
outlines: ["创作规划", "大纲/伏笔", "为每章维护目标、冲突与转折,并持续提醒尚未回收的伏笔。", "新建伏笔"],
|
|
7713
7867
|
relationships: ["跨章证据", "人物关系", "记录关系方向、阶段、置信度与原文依据。", "新建关系"],
|
|
7714
|
-
comments: ["正文协作", "
|
|
7868
|
+
comments: ["正文协作", "正文评论与待办", "集中查看并处理当前作品所有章节的评论与待办。", ""],
|
|
7715
7869
|
reviews: ["作者决策", "审核队列", "集中处理冲突、候选设定、低置信度关系和时间问题。", "新增审核项"],
|
|
7716
7870
|
tasks: ["AI 深度分析", "AI 分析中心", "对全书或指定章节运行人物关系、世界观、设定、事件与一致性分析。", "开始 AI 分析"],
|
|
7717
7871
|
"ai-settings": ["书籍提示词", "本书 AI 设置", "本书系统提示词会追加在内置提示词和平台全局提示词之后;任务默认模型只作用于当前作品。", "保存设置"]
|
|
@@ -9281,8 +9435,8 @@ async function renderWorkChapterComments(page = moduleListPages.comments) {
|
|
|
9281
9435
|
moduleListPages.comments = pageResult.page;
|
|
9282
9436
|
mountModuleCount(total);
|
|
9283
9437
|
$("#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("
|
|
9438
|
+
? `<div class="chapter-comment-module-list">${result.items.map((annotation) => chapterAnnotationCard(annotation, { showSource: true })).join("")}</div>${renderModulePagination(pageResult, "comments", "正文评论与待办列表")}`
|
|
9439
|
+
: emptyModule("还没有正文评论或待办", "在任一正文行上点击右键,即可添加评论或待办。");
|
|
9286
9440
|
bindModulePagination("comments", renderWorkChapterComments);
|
|
9287
9441
|
bindChapterAnnotationCards($("#module-content"), result.items, {
|
|
9288
9442
|
refresh: () => renderWorkChapterComments(pageResult.page),
|
|
@@ -11325,12 +11479,16 @@ async function ensureAiModelsLoaded() {
|
|
|
11325
11479
|
if (aiModelsLoadPromise && aiModelsLoadWorkId === workId) return aiModelsLoadPromise;
|
|
11326
11480
|
const select = $("#ai-model");
|
|
11327
11481
|
select.innerHTML = '<option value="">正在加载模型……</option>';
|
|
11482
|
+
syncAiModelPicker();
|
|
11328
11483
|
aiModelsLoadWorkId = workId;
|
|
11329
11484
|
aiModelsLoadPromise = loadModels();
|
|
11330
11485
|
try {
|
|
11331
11486
|
await aiModelsLoadPromise;
|
|
11332
11487
|
} catch (error) {
|
|
11333
|
-
if (state.work?.id === workId)
|
|
11488
|
+
if (state.work?.id === workId) {
|
|
11489
|
+
select.innerHTML = '<option value="">模型加载失败,点击重试</option>';
|
|
11490
|
+
syncAiModelPicker();
|
|
11491
|
+
}
|
|
11334
11492
|
throw error;
|
|
11335
11493
|
} finally {
|
|
11336
11494
|
if (aiModelsLoadWorkId === workId) {
|
|
@@ -11465,6 +11623,14 @@ function setAiContextDistributionVisible(visible) {
|
|
|
11465
11623
|
if (!visible && document.activeElement === $("#ai-context-popover-close")) meter.focus();
|
|
11466
11624
|
}
|
|
11467
11625
|
|
|
11626
|
+
function setAiModelPickerVisible(visible) {
|
|
11627
|
+
const button = $("#ai-model-picker");
|
|
11628
|
+
const popover = $("#ai-model-popover");
|
|
11629
|
+
popover.classList.toggle("hidden", !visible);
|
|
11630
|
+
button.setAttribute("aria-expanded", String(visible));
|
|
11631
|
+
if (!visible && document.activeElement === $("#ai-model")) button.focus();
|
|
11632
|
+
}
|
|
11633
|
+
|
|
11468
11634
|
function showAiContextWarning(usage = null) {
|
|
11469
11635
|
$("#ai-context-warning-title").textContent = "对话上下文过长";
|
|
11470
11636
|
$("#ai-context-warning-message").textContent = "当前对话上下文过长,是否进行压缩?不压缩可能会导致后续请求失败";
|
|
@@ -11491,13 +11657,17 @@ async function loadAiReferences() {
|
|
|
11491
11657
|
const workId = state.work?.id;
|
|
11492
11658
|
if (!workId) return;
|
|
11493
11659
|
const generation = workScopedUiGeneration;
|
|
11494
|
-
const [characters, settings] = await Promise.all([
|
|
11660
|
+
const [characters, settings, races, organizations] = await Promise.all([
|
|
11495
11661
|
canReadModule("characters") ? apiAllPages(`/api/works/${workId}/characters`) : Promise.resolve([]),
|
|
11496
|
-
canReadModule("settings") ? api(`/api/works/${workId}/settings/context`) : Promise.resolve([])
|
|
11662
|
+
canReadModule("settings") ? api(`/api/works/${workId}/settings/context`) : Promise.resolve([]),
|
|
11663
|
+
canReadModule("races") ? api(`/api/works/${workId}/races`) : Promise.resolve([]),
|
|
11664
|
+
canReadModule("organizations") ? apiAllPages(`/api/works/${workId}/organizations`) : Promise.resolve([])
|
|
11497
11665
|
]);
|
|
11498
11666
|
if (state.work?.id !== workId || generation !== workScopedUiGeneration) return;
|
|
11499
11667
|
state.characters = characters;
|
|
11500
11668
|
state.settings = settings;
|
|
11669
|
+
state.races = races;
|
|
11670
|
+
state.organizations = organizations;
|
|
11501
11671
|
renderAiRoleplayCharacterSelect();
|
|
11502
11672
|
loadedAiReferencesWorkId = workId;
|
|
11503
11673
|
}
|
|
@@ -14629,6 +14799,7 @@ async function sendAiWithOptions({ ignoreContextWarning = false } = {}) {
|
|
|
14629
14799
|
try {
|
|
14630
14800
|
try {
|
|
14631
14801
|
await ensureAiModelsLoaded();
|
|
14802
|
+
await ensureAiReferencesLoaded();
|
|
14632
14803
|
} catch (error) {
|
|
14633
14804
|
if (isAiRequestCancellation(error, requestHolder.snapshot) || !aiRequestTargetsCurrentState(requestHolder.snapshot)) throw error;
|
|
14634
14805
|
setAiChatTabStatus(tab, "error");
|
|
@@ -14876,6 +15047,7 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
14876
15047
|
}
|
|
14877
15048
|
});
|
|
14878
15049
|
let streamedText = "";
|
|
15050
|
+
let streamedPendingText = "";
|
|
14879
15051
|
let generatedMetadata = {};
|
|
14880
15052
|
let toolCalls = [];
|
|
14881
15053
|
let processSteps = [];
|
|
@@ -14886,7 +15058,6 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
14886
15058
|
let contextAction = "ready";
|
|
14887
15059
|
let warningOnly = false;
|
|
14888
15060
|
let streamContextCompacted = false;
|
|
14889
|
-
let finalAnswerStarted = false;
|
|
14890
15061
|
const processStartedAt = Date.now();
|
|
14891
15062
|
const elapsedProcessTime = () => Math.max(0, Date.now() - processStartedAt);
|
|
14892
15063
|
const processStepTypewriters = new Map();
|
|
@@ -14904,7 +15075,7 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
14904
15075
|
onRender: (text) => {
|
|
14905
15076
|
if (!aiRequestTargetsCurrentState(requestHolder.snapshot)) return;
|
|
14906
15077
|
processStepVisibleContents.set(step, text);
|
|
14907
|
-
renderStreamingProcessSteps(
|
|
15078
|
+
renderStreamingProcessSteps(false);
|
|
14908
15079
|
scrollAiFeedToBottom(feed);
|
|
14909
15080
|
}
|
|
14910
15081
|
});
|
|
@@ -14977,18 +15148,21 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
14977
15148
|
}
|
|
14978
15149
|
} else if (eventName === "delta") {
|
|
14979
15150
|
mountAssistantMessage();
|
|
14980
|
-
const firstFinalDelta = streamedText.length === 0;
|
|
14981
15151
|
const delta = typeof payload.delta === "string" ? payload.delta : "";
|
|
14982
15152
|
streamedText += delta;
|
|
14983
|
-
|
|
15153
|
+
streamedPendingText += delta;
|
|
14984
15154
|
typewriter.append(delta);
|
|
14985
|
-
if (firstFinalDelta && processSteps.length) renderStreamingProcessSteps(true, elapsedProcessTime());
|
|
14986
15155
|
meta.textContent = "正在生成回复……";
|
|
14987
15156
|
} else if (eventName === "process_step") {
|
|
14988
15157
|
mountAssistantMessage();
|
|
14989
15158
|
const step = { ...payload };
|
|
14990
15159
|
const append = step.append === true;
|
|
14991
15160
|
delete step.append;
|
|
15161
|
+
if (step.type === "intermediate" && typeof step.content === "string" && streamedPendingText.endsWith(step.content)) {
|
|
15162
|
+
streamedPendingText = streamedPendingText.slice(0, -step.content.length);
|
|
15163
|
+
streamedText = streamedText.slice(0, -step.content.length);
|
|
15164
|
+
typewriter.replace(streamedText);
|
|
15165
|
+
}
|
|
14992
15166
|
const existing = append ? processSteps.find((item) => item.id === step.id && item.type === step.type) : null;
|
|
14993
15167
|
if (existing && typeof step.content === "string") existing.content += step.content;
|
|
14994
15168
|
else processSteps.push(step);
|
|
@@ -14996,7 +15170,7 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
14996
15170
|
if (typeof step.content === "string" && step.content.length > 0 && step.type === "thinking") {
|
|
14997
15171
|
processStepTypewriter(targetStep).append(step.content);
|
|
14998
15172
|
}
|
|
14999
|
-
renderStreamingProcessSteps(
|
|
15173
|
+
renderStreamingProcessSteps(false, elapsedProcessTime());
|
|
15000
15174
|
meta.textContent = step.type === "thinking"
|
|
15001
15175
|
? `正在思考 · 第 ${Number(step.round) || 1} 轮`
|
|
15002
15176
|
: step.type === "context_compaction"
|
|
@@ -15011,7 +15185,7 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
15011
15185
|
if (toolCall.status === "failed") setAiChatTabStatus(tab, "error");
|
|
15012
15186
|
toolCalls.push(toolCall);
|
|
15013
15187
|
processSteps.push(aiToolProcessStep(toolCall, round));
|
|
15014
|
-
renderStreamingProcessSteps(
|
|
15188
|
+
renderStreamingProcessSteps(false, elapsedProcessTime());
|
|
15015
15189
|
meta.textContent = `已调用 ${toolCalls.length} 个工具,正在等待模型处理结果`;
|
|
15016
15190
|
scrollAiFeedToBottom(feed);
|
|
15017
15191
|
} else if (eventName === "context_compacted") {
|
|
@@ -15123,7 +15297,9 @@ function appendMessage(role, text, citations = [], createdAt = null, metadata =
|
|
|
15123
15297
|
});
|
|
15124
15298
|
const heading = attachMessageHeading(
|
|
15125
15299
|
message,
|
|
15126
|
-
role === "user"
|
|
15300
|
+
role === "user"
|
|
15301
|
+
? aiUserLabel(tab?.roleplayUserCharacter)
|
|
15302
|
+
: aiAssistantLabel(isInterrupted ? aiStreamInterruptionLabel(interruptionCode) : "", tab?.roleplayCharacter),
|
|
15127
15303
|
createdAt ?? undefined,
|
|
15128
15304
|
tab
|
|
15129
15305
|
);
|
|
@@ -15135,20 +15311,27 @@ function appendMessage(role, text, citations = [], createdAt = null, metadata =
|
|
|
15135
15311
|
failureBadge.setAttribute("aria-label", `消息状态:${isInterrupted ? aiStreamInterruptionLabel(interruptionCode) : "失败"}`);
|
|
15136
15312
|
heading.firstElementChild?.append(failureBadge);
|
|
15137
15313
|
}
|
|
15138
|
-
const
|
|
15139
|
-
?
|
|
15314
|
+
const mentionGroups = role === "user"
|
|
15315
|
+
? [
|
|
15316
|
+
["角色", metadata?.mentionCharacterIds, state.characters],
|
|
15317
|
+
["种族", metadata?.mentionRaceIds, state.races],
|
|
15318
|
+
["组织", metadata?.mentionOrganizationIds, state.organizations]
|
|
15319
|
+
]
|
|
15320
|
+
.flatMap(([kind, ids, items]) => userMessageMentionNames(ids, items).map((name) => ({ kind, name })))
|
|
15140
15321
|
: [];
|
|
15141
|
-
if (
|
|
15322
|
+
if (mentionGroups.length) {
|
|
15142
15323
|
const references = document.createElement("div");
|
|
15143
15324
|
references.className = "user-message-mentions";
|
|
15144
15325
|
const label = document.createElement("span");
|
|
15145
15326
|
label.className = "user-message-mentions-label";
|
|
15146
|
-
label.textContent = "
|
|
15327
|
+
label.textContent = "引用";
|
|
15147
15328
|
references.append(label);
|
|
15148
|
-
for (const name of
|
|
15329
|
+
for (const { kind, name } of mentionGroups) {
|
|
15149
15330
|
const reference = document.createElement("span");
|
|
15150
15331
|
reference.className = "user-message-mention";
|
|
15151
15332
|
reference.textContent = name;
|
|
15333
|
+
reference.title = `${kind}:${name}`;
|
|
15334
|
+
reference.setAttribute("aria-label", `${kind}:${name}`);
|
|
15152
15335
|
references.append(reference);
|
|
15153
15336
|
}
|
|
15154
15337
|
message.append(references);
|
|
@@ -15174,6 +15357,7 @@ function appendMessage(role, text, citations = [], createdAt = null, metadata =
|
|
|
15174
15357
|
if (role === "assistant") {
|
|
15175
15358
|
renderAiProcessSteps(message, processSteps, true, processDurationMs);
|
|
15176
15359
|
}
|
|
15360
|
+
if (role === "user") attachUserCopyAction(message, text);
|
|
15177
15361
|
if (role === "assistant" && !text.startsWith("调用失败:")) {
|
|
15178
15362
|
const selectedModelId = tab?.modelId ?? tab?.selectedModelId ?? $("#ai-model").value;
|
|
15179
15363
|
const selectedModel = state.models.find((model) => model.id === selectedModelId) ?? state.models[0];
|
|
@@ -16648,6 +16832,7 @@ $("#chapter-content").addEventListener("contextmenu", (event) => {
|
|
|
16648
16832
|
showLineCitationMenu(event, lineIndexAtPointer(event.clientY));
|
|
16649
16833
|
});
|
|
16650
16834
|
$("#chapter-line-numbers-inner").addEventListener("pointerdown", (event) => {
|
|
16835
|
+
if (event.target.closest(".chapter-line-annotation-count")) return;
|
|
16651
16836
|
const row = event.target.closest(".chapter-line-number");
|
|
16652
16837
|
if (!row || event.button !== 0) return;
|
|
16653
16838
|
event.preventDefault();
|
|
@@ -16670,7 +16855,23 @@ const finishChapterLineDrag = (event) => {
|
|
|
16670
16855
|
};
|
|
16671
16856
|
$("#chapter-line-numbers-inner").addEventListener("pointerup", finishChapterLineDrag);
|
|
16672
16857
|
$("#chapter-line-numbers-inner").addEventListener("pointercancel", finishChapterLineDrag);
|
|
16858
|
+
$("#chapter-line-numbers-inner").addEventListener("click", (event) => {
|
|
16859
|
+
const bubble = event.target.closest(".chapter-line-annotation-count");
|
|
16860
|
+
if (bubble) {
|
|
16861
|
+
event.preventDefault();
|
|
16862
|
+
event.stopPropagation();
|
|
16863
|
+
openChapterAnnotationsDialog(Number(bubble.dataset.lineIndex)).catch((error) => toast(error.message, "error"));
|
|
16864
|
+
return;
|
|
16865
|
+
}
|
|
16866
|
+
const lineSelect = event.target.closest(".chapter-line-number-select");
|
|
16867
|
+
if (!lineSelect) return;
|
|
16868
|
+
const lineIndex = Number(lineSelect.closest(".chapter-line-number")?.dataset.lineIndex);
|
|
16869
|
+
if (!Number.isInteger(lineIndex)) return;
|
|
16870
|
+
paintChapterLineSelection(lineIndex, lineIndex);
|
|
16871
|
+
selectChapterLines(lineIndex, lineIndex);
|
|
16872
|
+
});
|
|
16673
16873
|
$("#chapter-line-numbers-inner").addEventListener("contextmenu", (event) => {
|
|
16874
|
+
if (event.target.closest(".chapter-line-annotation-count")) return;
|
|
16674
16875
|
const row = event.target.closest(".chapter-line-number");
|
|
16675
16876
|
if (row) showLineCitationMenu(event, Number(row.dataset.lineIndex));
|
|
16676
16877
|
});
|
|
@@ -16680,7 +16881,10 @@ $("#add-line-todo").addEventListener("click", () => createSelectedLineAnnotation
|
|
|
16680
16881
|
$("#chapter-annotations-button").addEventListener("click", () => openChapterAnnotationsDialog().catch((error) => toast(error.message, "error")));
|
|
16681
16882
|
$("#chapter-annotations-close").addEventListener("click", () => $("#chapter-annotations-dialog").close());
|
|
16682
16883
|
$("#chapter-annotations-done").addEventListener("click", () => $("#chapter-annotations-dialog").close());
|
|
16683
|
-
$("#chapter-annotations-refresh").addEventListener("click", () =>
|
|
16884
|
+
$("#chapter-annotations-refresh").addEventListener("click", () => Promise.all([
|
|
16885
|
+
loadChapterAnnotationCounts(),
|
|
16886
|
+
loadChapterAnnotations(chapterAnnotationsLineIndex)
|
|
16887
|
+
]).catch((error) => toast(error.message, "error")));
|
|
16684
16888
|
$("#left-panel-toggle").addEventListener("click", () => {
|
|
16685
16889
|
panelLayout.leftCollapsed = !panelLayout.leftCollapsed;
|
|
16686
16890
|
applyPanelLayout(true);
|
|
@@ -16718,6 +16922,8 @@ $("#ai-assistant-entry").addEventListener("click", () => {
|
|
|
16718
16922
|
if (isMobileViewport()) {
|
|
16719
16923
|
panelLayout.leftCollapsed = true;
|
|
16720
16924
|
applyPanelLayout(true);
|
|
16925
|
+
setAiConversationWorkspaceVisible(true);
|
|
16926
|
+
return;
|
|
16721
16927
|
}
|
|
16722
16928
|
setAiConversationWorkspaceVisible(true);
|
|
16723
16929
|
});
|
|
@@ -16755,7 +16961,7 @@ $("#ai-prompt").addEventListener("input", async () => {
|
|
|
16755
16961
|
$("#ai-prompt").addEventListener("focus", () => {
|
|
16756
16962
|
ensureAiModelsLoaded().catch((error) => toast(`模型加载失败:${error.message}`, "error"));
|
|
16757
16963
|
});
|
|
16758
|
-
for (const select of [$("#ai-task"), $("#ai-scope"), $("#ai-roleplay-character"), $("#ai-model")]) {
|
|
16964
|
+
for (const select of [$("#ai-task"), $("#ai-scope"), $("#ai-roleplay-character"), $("#ai-roleplay-user-character"), $("#ai-model")]) {
|
|
16759
16965
|
select.addEventListener("pointerdown", blockLockedAiConversationOptionInteraction);
|
|
16760
16966
|
select.addEventListener("click", blockLockedAiConversationOptionInteraction);
|
|
16761
16967
|
select.addEventListener("keydown", blockLockedAiConversationOptionKeydown);
|
|
@@ -16766,9 +16972,26 @@ $("#ai-model").addEventListener("focus", () => {
|
|
|
16766
16972
|
$("#ai-model").addEventListener("change", (event) => {
|
|
16767
16973
|
if (state.aiPromptSent) {
|
|
16768
16974
|
event.currentTarget.value = state.aiConversationModelId ?? event.currentTarget.value;
|
|
16975
|
+
syncAiModelPicker();
|
|
16769
16976
|
return toast(aiConversationOptionLockedMessage);
|
|
16770
16977
|
}
|
|
16771
16978
|
setAiContextMeter(null);
|
|
16979
|
+
syncAiModelPicker();
|
|
16980
|
+
setAiModelPickerVisible(false);
|
|
16981
|
+
});
|
|
16982
|
+
$("#ai-model-picker").addEventListener("click", async (event) => {
|
|
16983
|
+
const button = event.currentTarget;
|
|
16984
|
+
if (notifyAiConversationOptionLocked(button)) return;
|
|
16985
|
+
const willOpen = $("#ai-model-popover").classList.contains("hidden");
|
|
16986
|
+
setAiContextDistributionVisible(false);
|
|
16987
|
+
setAiModelPickerVisible(willOpen);
|
|
16988
|
+
if (!willOpen) return;
|
|
16989
|
+
try {
|
|
16990
|
+
await ensureAiModelsLoaded();
|
|
16991
|
+
} catch (error) {
|
|
16992
|
+
toast(`模型加载失败:${error.message}`, "error");
|
|
16993
|
+
}
|
|
16994
|
+
if (!$("#ai-model-popover").classList.contains("hidden") && !$("#ai-model").disabled) $("#ai-model").focus();
|
|
16772
16995
|
});
|
|
16773
16996
|
$("#ai-roleplay-character").addEventListener("focus", async () => {
|
|
16774
16997
|
try {
|
|
@@ -16795,6 +17018,31 @@ $("#ai-roleplay-character").addEventListener("change", async (event) => {
|
|
|
16795
17018
|
renderAiRoleplayCharacterSelect();
|
|
16796
17019
|
}
|
|
16797
17020
|
});
|
|
17021
|
+
$("#ai-roleplay-user-character").addEventListener("focus", async () => {
|
|
17022
|
+
try {
|
|
17023
|
+
await ensureAiReferencesLoaded();
|
|
17024
|
+
renderAiRoleplayUserCharacterSelect();
|
|
17025
|
+
} catch (error) {
|
|
17026
|
+
toast(`角色卡加载失败:${error.message}`, "error");
|
|
17027
|
+
}
|
|
17028
|
+
});
|
|
17029
|
+
$("#ai-roleplay-user-character").addEventListener("change", async (event) => {
|
|
17030
|
+
const select = event.currentTarget;
|
|
17031
|
+
if (state.aiPromptSent) {
|
|
17032
|
+
renderAiRoleplayUserCharacterSelect();
|
|
17033
|
+
return toast(aiConversationOptionLockedMessage);
|
|
17034
|
+
}
|
|
17035
|
+
const userCharacterId = select.value;
|
|
17036
|
+
select.disabled = true;
|
|
17037
|
+
try {
|
|
17038
|
+
await updateAiRoleplayCharacter($("#ai-roleplay-character").value, userCharacterId);
|
|
17039
|
+
} catch (error) {
|
|
17040
|
+
renderAiRoleplayUserCharacterSelect();
|
|
17041
|
+
toast(`关系扮演模式切换失败:${error.message}`, "error");
|
|
17042
|
+
} finally {
|
|
17043
|
+
renderAiRoleplayUserCharacterSelect();
|
|
17044
|
+
}
|
|
17045
|
+
});
|
|
16798
17046
|
$("#ai-task").addEventListener("change", async (event) => {
|
|
16799
17047
|
const select = event.currentTarget;
|
|
16800
17048
|
const previousTaskType = select.dataset.previousValue || state.aiTaskType || "chat";
|
|
@@ -16989,6 +17237,7 @@ document.addEventListener("pointerdown", (event) => {
|
|
|
16989
17237
|
if (!event.target.closest("#ai-conversation-switcher-menu") && !event.target.closest("#ai-conversation-switcher")) setAiConversationSwitcherVisible(false);
|
|
16990
17238
|
if (!event.target.closest(".prompt-composer")) hideAiMentionMenu();
|
|
16991
17239
|
if (!event.target.closest("#ai-context-meter") && !event.target.closest("#ai-context-popover")) setAiContextDistributionVisible(false);
|
|
17240
|
+
if (!event.target.closest("#ai-model-picker") && !event.target.closest("#ai-model-popover")) setAiModelPickerVisible(false);
|
|
16992
17241
|
if (!event.target.closest("#account-button") && !event.target.closest("#account-menu")) {
|
|
16993
17242
|
$("#account-menu").classList.add("hidden");
|
|
16994
17243
|
$("#account-button").setAttribute("aria-expanded", "false");
|
|
@@ -17006,6 +17255,10 @@ document.addEventListener("keydown", (event) => {
|
|
|
17006
17255
|
return;
|
|
17007
17256
|
}
|
|
17008
17257
|
if (event.key === "Escape") {
|
|
17258
|
+
if (!$("#ai-model-popover").classList.contains("hidden")) {
|
|
17259
|
+
setAiModelPickerVisible(false);
|
|
17260
|
+
return;
|
|
17261
|
+
}
|
|
17009
17262
|
if (!$("#chapter-search-panel").classList.contains("hidden")) {
|
|
17010
17263
|
closeChapterSearchPanel();
|
|
17011
17264
|
return;
|
|
@@ -17041,6 +17294,7 @@ document.addEventListener("keydown", (event) => {
|
|
|
17041
17294
|
openSearchDialog().catch((error) => toast(error.message, "error"));
|
|
17042
17295
|
}, { capture: true });
|
|
17043
17296
|
$("#ai-context-meter").addEventListener("click", () => {
|
|
17297
|
+
setAiModelPickerVisible(false);
|
|
17044
17298
|
setAiContextDistributionVisible($("#ai-context-popover").classList.contains("hidden"));
|
|
17045
17299
|
});
|
|
17046
17300
|
$("#ai-context-popover-close").addEventListener("click", () => setAiContextDistributionVisible(false));
|