@musnows/scriverse 0.9.8 → 0.9.10
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-write-plans.js +136 -48
- package/dist/ai-write-plans.js.map +1 -1
- package/dist/ai.js +404 -100
- package/dist/ai.js.map +1 -1
- package/dist/app.js +366 -2
- package/dist/app.js.map +1 -1
- package/dist/cli-core.js +95 -3
- package/dist/cli-core.js.map +1 -1
- package/dist/database.js +549 -4
- package/dist/database.js.map +1 -1
- package/dist/im-orchestrator.js +1317 -0
- package/dist/im-orchestrator.js.map +1 -0
- package/dist/im.js +1690 -0
- package/dist/im.js.map +1 -0
- package/dist/public/ai-interactive.js +14 -13
- package/dist/public/app.js +290 -84
- package/dist/public/im.d.ts +71 -0
- package/dist/public/im.js +1899 -0
- package/dist/public/index.html +108 -5
- package/dist/public/keyboard-shortcuts.d.ts +1 -0
- package/dist/public/keyboard-shortcuts.js +9 -0
- package/dist/public/page-route.d.ts +2 -1
- package/dist/public/page-route.js +4 -1
- package/dist/public/styles.css +336 -4
- package/dist/s3-backup.js +5 -1
- package/dist/s3-backup.js.map +1 -1
- package/dist/security.js +1 -0
- package/dist/security.js.map +1 -1
- package/dist/store.js +4 -1
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +9 -4
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
package/dist/public/app.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
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 { formatDateTime, normalizeParagraphSpacing } from "/text-formatting.js?v=20260713-saved-at-seconds";
|
|
3
3
|
import { renderMarkdown } from "/markdown.js?v=20260830-adjacent-blockquotes-v1";
|
|
4
|
+
import { createImWorkspace } from "/im.js?v=20260903-global-im-v103";
|
|
4
5
|
import { findAiMention, listAiMentionOptions, mergeAiReferenceScope, userMessageMentionNames } from "/ai-mentions.js?v=20260811-user-message-mentions-v1";
|
|
5
6
|
import { applyAiSkillCommand, findAiSkillCommand, listAiSkillOptions } from "/ai-skill-menu.js?v=20260830-ai-skill-slash-menu-v1";
|
|
6
7
|
import {
|
|
@@ -47,7 +48,7 @@ import {
|
|
|
47
48
|
renderWritePlanDetailMarkup,
|
|
48
49
|
isInteractiveToolPending,
|
|
49
50
|
aiFormatDateTime
|
|
50
|
-
} from "/ai-interactive.js?v=
|
|
51
|
+
} from "/ai-interactive.js?v=20260903-question-batch-v7";
|
|
51
52
|
import { copyAiRawMarkdown } from "/ai-message-actions.js?v=20260713-copy-raw-markdown";
|
|
52
53
|
import { bindPlainTextPaste } from "/plain-text-paste.js?v=20260815-plain-text-paste-v1";
|
|
53
54
|
import { clipboardImageFiles } from "/character-markdown.js?v=20260820-ai-chat-image-attachments-v1";
|
|
@@ -79,7 +80,7 @@ import {
|
|
|
79
80
|
characterGenderLabel,
|
|
80
81
|
characterStateFieldLabel
|
|
81
82
|
} from "/display-labels.js?v=20260816-character-gender-v1&feature=global-replace-volume-v1&feature=ai-provider-responses-v1";
|
|
82
|
-
import { parsePageRoute, serializePageRoute } from "/page-route.js?v=20260812-reader-preview-v1";
|
|
83
|
+
import { parsePageRoute, serializePageRoute } from "/page-route.js?v=20260812-reader-preview-v1&feature=global-im-return-v1";
|
|
83
84
|
import {
|
|
84
85
|
READING_PREFERENCES_STORAGE_KEY,
|
|
85
86
|
READING_PREFERENCES_VERSION,
|
|
@@ -100,7 +101,7 @@ import { buildRaceForest, eligibleRaceParents, orderRaceFilterOptions, racePathL
|
|
|
100
101
|
import { ANALYSIS_TYPES, analysisTypeDescription } from "/analysis-types.js?v=20260721-analysis-descriptions";
|
|
101
102
|
import { WORK_PERMISSION_MODULES, canReadPermissionModule, canReadUiModule, canWritePermissionModule, canWriteUiModule, emptyModulePermissions, firstReadableUiModule, normalizeModulePermissions, permissionSummary } from "/work-permissions.js?v=20260818-annotation-permissions-v1";
|
|
102
103
|
import { MODULE_LAYOUT_STORAGE_KEY, LEGACY_SETTINGS_LAYOUT_STORAGE_KEY, normalizeModuleLayout } from "/module-layout.js?v=20260723-module-layout-toggle";
|
|
103
|
-
import { isGlobalSearchShortcut } from "/keyboard-shortcuts.js?v=
|
|
104
|
+
import { isGlobalSearchShortcut, isSaveShortcut } from "/keyboard-shortcuts.js?v=20260831-chapter-save-shortcut-v2";
|
|
104
105
|
import { prioritizeGlobalSearchResults, resolveGlobalSearchTarget, splitGlobalSearchHighlight } from "/global-search.js?v=20260804-agent-history-score-sort-v1";
|
|
105
106
|
import { filterCharacters, paginateCharacters } from "/character-filters.js?v=20260817-character-filter-state-v1";
|
|
106
107
|
import { filterRelationships, sortCharacterRelationships } from "/relationship-filters.js?v=20260818-character-relationship-group-v1";
|
|
@@ -305,6 +306,7 @@ let relationshipSearchIndexRefreshTimer = null;
|
|
|
305
306
|
let semanticSearchIndexRefreshTimer = null;
|
|
306
307
|
let aiSemanticSearchResults = [];
|
|
307
308
|
let aiSemanticSearchQuery = "";
|
|
309
|
+
let aiCitationPopoverTrigger = null;
|
|
308
310
|
let backgroundTaskCenterTimer = null;
|
|
309
311
|
let backgroundTaskCenterRequest = 0;
|
|
310
312
|
let backgroundTaskCenterWorkId = null;
|
|
@@ -1031,7 +1033,7 @@ function replacePageRoute(route) {
|
|
|
1031
1033
|
}
|
|
1032
1034
|
|
|
1033
1035
|
function presencePageForRoute(route = currentPageRoute()) {
|
|
1034
|
-
if (!state.work || route.view === "shelf" || route.view === "platform-ai" || route.view === "platform-usage") return null;
|
|
1036
|
+
if (!state.work || route.view === "shelf" || route.view === "im" || route.view === "platform-ai" || route.view === "platform-usage") return null;
|
|
1035
1037
|
if (route.view === "reader") return { kind: "welcome" };
|
|
1036
1038
|
if (relationshipPresenceId) return { kind: "entity-editor", module: "relationship", resourceId: relationshipPresenceId };
|
|
1037
1039
|
if (route.view === "editor") return { kind: "editor", resourceId: String(route.chapterId ?? "") || undefined };
|
|
@@ -1226,6 +1228,7 @@ async function confirmConcurrentSave() {
|
|
|
1226
1228
|
|
|
1227
1229
|
function currentPageRoute() {
|
|
1228
1230
|
const workId = state.work?.id ?? null;
|
|
1231
|
+
if (!$("#im-view").classList.contains("hidden")) return { view: "im" };
|
|
1229
1232
|
if (!$("#reader-view").classList.contains("hidden") && workId) {
|
|
1230
1233
|
return { view: "reader", workId, chapterId: readingTargetChapter?.id ?? null };
|
|
1231
1234
|
}
|
|
@@ -2071,6 +2074,66 @@ function selectChapterLines(start, end) {
|
|
|
2071
2074
|
return selection;
|
|
2072
2075
|
}
|
|
2073
2076
|
|
|
2077
|
+
function aiCitationLineLabel(citation) {
|
|
2078
|
+
return citation.startLine === citation.endLine ? `第 ${citation.startLine} 行` : `第 ${citation.startLine}-${citation.endLine} 行`;
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
function aiCitationTagLabel(citation) {
|
|
2082
|
+
return `${citation.chapterTitle} · L${citation.startLine}${citation.endLine === citation.startLine ? "" : `-L${citation.endLine}`}`;
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
function positionAiCitationPopover(trigger) {
|
|
2086
|
+
const panel = $("#ai-panel");
|
|
2087
|
+
const popover = $("#ai-citation-popover");
|
|
2088
|
+
if (!panel || !popover || !trigger?.isConnected || popover.classList.contains("hidden")) return;
|
|
2089
|
+
const panelRect = panel.getBoundingClientRect();
|
|
2090
|
+
const triggerRect = trigger.getBoundingClientRect();
|
|
2091
|
+
const gutter = 16;
|
|
2092
|
+
const maxLeft = Math.max(gutter, panelRect.width - popover.offsetWidth - gutter);
|
|
2093
|
+
const anchorCenter = triggerRect.left + triggerRect.width / 2 - panelRect.left;
|
|
2094
|
+
const left = Math.min(maxLeft, Math.max(gutter, anchorCenter - popover.offsetWidth / 2));
|
|
2095
|
+
const below = triggerRect.bottom - panelRect.top + 8;
|
|
2096
|
+
const above = triggerRect.top - panelRect.top - popover.offsetHeight - 8;
|
|
2097
|
+
const maxTop = Math.max(gutter, panelRect.height - popover.offsetHeight - gutter);
|
|
2098
|
+
const fitsBelow = below <= maxTop;
|
|
2099
|
+
const top = fitsBelow ? below : Math.max(gutter, Math.min(above, maxTop));
|
|
2100
|
+
const arrowOffset = Math.max(12, Math.min(popover.offsetWidth - 12, anchorCenter - left));
|
|
2101
|
+
popover.dataset.placement = fitsBelow ? "bottom" : "top";
|
|
2102
|
+
popover.style.left = `${left}px`;
|
|
2103
|
+
popover.style.top = `${top}px`;
|
|
2104
|
+
popover.style.setProperty("--ai-citation-arrow-offset", `${arrowOffset}px`);
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
function closeAiCitationPopover({ restoreFocus = false } = {}) {
|
|
2108
|
+
const popover = $("#ai-citation-popover");
|
|
2109
|
+
if (!popover) return;
|
|
2110
|
+
const trigger = aiCitationPopoverTrigger;
|
|
2111
|
+
aiCitationPopoverTrigger = null;
|
|
2112
|
+
popover.classList.add("hidden");
|
|
2113
|
+
popover.setAttribute("aria-hidden", "true");
|
|
2114
|
+
popover.removeAttribute("data-placement");
|
|
2115
|
+
popover.style.removeProperty("left");
|
|
2116
|
+
popover.style.removeProperty("top");
|
|
2117
|
+
popover.style.removeProperty("--ai-citation-arrow-offset");
|
|
2118
|
+
trigger?.setAttribute("aria-expanded", "false");
|
|
2119
|
+
if (restoreFocus && trigger?.isConnected) trigger.focus({ preventScroll: true });
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
function openAiCitationPopover(citation, trigger) {
|
|
2123
|
+
const popover = $("#ai-citation-popover");
|
|
2124
|
+
if (!popover || !trigger) return;
|
|
2125
|
+
closeAiCitationPopover();
|
|
2126
|
+
$("#ai-citation-popover-title").textContent = String(citation.chapterTitle ?? "正文引用");
|
|
2127
|
+
$("#ai-citation-popover-description").textContent = aiCitationLineLabel(citation);
|
|
2128
|
+
$("#ai-citation-popover-quote").textContent = citation.text || "(空白行)";
|
|
2129
|
+
aiCitationPopoverTrigger = trigger;
|
|
2130
|
+
trigger.setAttribute("aria-expanded", "true");
|
|
2131
|
+
popover.classList.remove("hidden");
|
|
2132
|
+
popover.setAttribute("aria-hidden", "false");
|
|
2133
|
+
positionAiCitationPopover(trigger);
|
|
2134
|
+
$("#ai-citation-popover-close").focus({ preventScroll: true });
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2074
2137
|
function renderAiCitations() {
|
|
2075
2138
|
const host = $("#ai-citations");
|
|
2076
2139
|
host.replaceChildren();
|
|
@@ -2084,7 +2147,7 @@ function renderAiCitations() {
|
|
|
2084
2147
|
const source = document.createElement("strong");
|
|
2085
2148
|
source.textContent = citation.chapterTitle;
|
|
2086
2149
|
const range = document.createElement("small");
|
|
2087
|
-
range.textContent = citation
|
|
2150
|
+
range.textContent = aiCitationLineLabel(citation);
|
|
2088
2151
|
const excerpt = document.createElement("span");
|
|
2089
2152
|
excerpt.textContent = citation.text.replace(/\s+/gu, " ").trim() || "空白行";
|
|
2090
2153
|
main.append(source, range, excerpt);
|
|
@@ -2092,7 +2155,7 @@ function renderAiCitations() {
|
|
|
2092
2155
|
const remove = document.createElement("button");
|
|
2093
2156
|
remove.type = "button";
|
|
2094
2157
|
remove.className = "ai-citation-remove";
|
|
2095
|
-
remove.setAttribute("aria-label", `移除 ${citation.chapterTitle}
|
|
2158
|
+
remove.setAttribute("aria-label", `移除 ${citation.chapterTitle} ${aiCitationLineLabel(citation)}引用`);
|
|
2096
2159
|
remove.textContent = "×";
|
|
2097
2160
|
remove.addEventListener("click", () => {
|
|
2098
2161
|
state.aiCitations = state.aiCitations.filter((item) => item.id !== citation.id);
|
|
@@ -2610,6 +2673,7 @@ function renderAiChatTabs() {
|
|
|
2610
2673
|
function activateAiChatTab(tabId, { persistCurrent = true, force = false } = {}) {
|
|
2611
2674
|
const current = activeAiChatTab();
|
|
2612
2675
|
if (!force && current?.id === tabId) return current;
|
|
2676
|
+
closeAiCitationPopover();
|
|
2613
2677
|
if (persistCurrent) persistActiveAiChatTab();
|
|
2614
2678
|
const tab = aiChatTabManager.activate(tabId);
|
|
2615
2679
|
if (!tab) return null;
|
|
@@ -2700,6 +2764,7 @@ function resetAiFeed(
|
|
|
2700
2764
|
roleplayCharacter = state.aiRoleplayCharacter,
|
|
2701
2765
|
roleplayUserCharacter = state.aiRoleplayUserCharacter
|
|
2702
2766
|
) {
|
|
2767
|
+
closeAiCitationPopover();
|
|
2703
2768
|
const tab = aiChatTabManager.get(feed?.dataset.aiTabId);
|
|
2704
2769
|
if (tab) tab.lastMessageAt = null;
|
|
2705
2770
|
if (isActiveAiChatTab(tab)) state.aiLastMessageAt = null;
|
|
@@ -2940,7 +3005,8 @@ const AI_TOOL_DISPLAY_NAMES = {
|
|
|
2940
3005
|
recall_story: "回忆故事",
|
|
2941
3006
|
recall_roleplay_memory: "回忆当前扮演线",
|
|
2942
3007
|
remember_roleplay: "整理扮演记忆",
|
|
2943
|
-
calculate_time: "计算日期"
|
|
3008
|
+
calculate_time: "计算日期",
|
|
3009
|
+
ask_user_question: "向作者提问"
|
|
2944
3010
|
};
|
|
2945
3011
|
|
|
2946
3012
|
const AI_TOOL_DESCRIPTIONS = {
|
|
@@ -2958,7 +3024,8 @@ const AI_TOOL_DESCRIPTIONS = {
|
|
|
2958
3024
|
recall_story: "查询自己姓名或别名出现过的正文段落,避免全知回忆。",
|
|
2959
3025
|
recall_roleplay_memory: "查询当前所扮演角色在作品内唯一共享的非正史记忆库,不读取其他角色或作品正史。",
|
|
2960
3026
|
remember_roleplay: "暂存本轮值得保持的扮演经历;最终角色回复成功保存后才提交。",
|
|
2961
|
-
calculate_time: "计算两个 YYYY-MM-DD 日期之间的天数差。"
|
|
3027
|
+
calculate_time: "计算两个 YYYY-MM-DD 日期之间的天数差。",
|
|
3028
|
+
ask_user_question: "向作者批量提出单选问题;作者一次提交的全部选择会作为工具结果返回给 Agent。"
|
|
2962
3029
|
};
|
|
2963
3030
|
|
|
2964
3031
|
const aiFeedScrollFrames = new WeakMap();
|
|
@@ -3027,8 +3094,9 @@ function bindAiFeedAutoScroll(feed) {
|
|
|
3027
3094
|
update();
|
|
3028
3095
|
}
|
|
3029
3096
|
|
|
3030
|
-
function scrollAiFeedToBottom(feed = $("#ai-feed")) {
|
|
3097
|
+
function scrollAiFeedToBottom(feed = $("#ai-feed"), { force = false } = {}) {
|
|
3031
3098
|
bindAiFeedAutoScroll(feed);
|
|
3099
|
+
if (force) aiFeedAutoScrollStates.set(feed, true);
|
|
3032
3100
|
if (!aiFeedAutoScrollStates.get(feed)) return;
|
|
3033
3101
|
feed.scrollTop = feed.scrollHeight;
|
|
3034
3102
|
const currentFrame = aiFeedScrollFrames.get(feed);
|
|
@@ -3121,7 +3189,7 @@ function openAiToolCallDetail(toolCall) {
|
|
|
3121
3189
|
|
|
3122
3190
|
function createAiToolCallButton(toolCall) {
|
|
3123
3191
|
const name = String(toolCall?.name ?? "unknown");
|
|
3124
|
-
if (name === "propose_write_plan"
|
|
3192
|
+
if (name === "propose_write_plan") {
|
|
3125
3193
|
const card = createInteractiveToolCard(toolCall, AI_TOOL_CARD_ACTIONS);
|
|
3126
3194
|
if (card) return card;
|
|
3127
3195
|
}
|
|
@@ -3146,6 +3214,8 @@ let currentPlanDialogFocusConfirm = false;
|
|
|
3146
3214
|
let aiQuestionDialogQuestionId = null;
|
|
3147
3215
|
let currentAiQuestionDialogView = null;
|
|
3148
3216
|
let aiQuestionDialogBusy = false;
|
|
3217
|
+
let aiQuestionDialogIndex = 0;
|
|
3218
|
+
let aiQuestionDrafts = [];
|
|
3149
3219
|
const aiQuestionContinuationTabIds = new Set();
|
|
3150
3220
|
let autoOpenedQuestionIds = new Set();
|
|
3151
3221
|
const aiApprovalCenterState = { status: "" };
|
|
@@ -3317,6 +3387,47 @@ async function fetchAiUserQuestion(questionId) {
|
|
|
3317
3387
|
return question;
|
|
3318
3388
|
}
|
|
3319
3389
|
|
|
3390
|
+
function aiQuestionItems(question) {
|
|
3391
|
+
const items = Array.isArray(question?.questions) && question.questions.length > 0
|
|
3392
|
+
? question.questions
|
|
3393
|
+
: [{
|
|
3394
|
+
question: question?.question ?? "",
|
|
3395
|
+
options: question?.options ?? [],
|
|
3396
|
+
selectedOption: question?.selectedOption ?? null,
|
|
3397
|
+
customAnswer: question?.customAnswer ?? "",
|
|
3398
|
+
answerText: question?.answerText ?? "",
|
|
3399
|
+
isCustomAnswer: question?.isCustomAnswer === true
|
|
3400
|
+
}];
|
|
3401
|
+
return items.map((item, index) => ({
|
|
3402
|
+
...item,
|
|
3403
|
+
index,
|
|
3404
|
+
question: String(item?.question ?? ""),
|
|
3405
|
+
options: Array.isArray(item?.options) ? item.options : []
|
|
3406
|
+
}));
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
function resetAiQuestionDialogState(question) {
|
|
3410
|
+
const items = aiQuestionItems(question);
|
|
3411
|
+
aiQuestionDialogIndex = 0;
|
|
3412
|
+
aiQuestionDrafts = items.map((item) => ({
|
|
3413
|
+
selectedOption: Number.isInteger(item.selectedOption) ? Number(item.selectedOption) : null,
|
|
3414
|
+
customAnswer: String(item.customAnswer ?? "")
|
|
3415
|
+
}));
|
|
3416
|
+
}
|
|
3417
|
+
|
|
3418
|
+
function updateCurrentAiQuestionDraft() {
|
|
3419
|
+
if (currentAiQuestionDialogView?.status !== "pending") return;
|
|
3420
|
+
const draft = aiQuestionDrafts[aiQuestionDialogIndex];
|
|
3421
|
+
if (!draft) return;
|
|
3422
|
+
const checked = document.querySelector('input[name="ai-question-choice"]:checked');
|
|
3423
|
+
draft.selectedOption = checked && checked.value !== "custom" ? Number(checked.value) : null;
|
|
3424
|
+
draft.customAnswer = String($("#ai-question-custom-answer").value ?? "");
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3427
|
+
function aiQuestionDraftIsAnswered(draft) {
|
|
3428
|
+
return Boolean(draft && (Number.isInteger(draft.selectedOption) || String(draft.customAnswer ?? "").trim()));
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3320
3431
|
function syncAiQuestionOptionPresentation() {
|
|
3321
3432
|
const checked = document.querySelector('input[name="ai-question-choice"]:checked');
|
|
3322
3433
|
for (const label of document.querySelectorAll(".ai-question-option-item")) {
|
|
@@ -3337,8 +3448,23 @@ function renderAiUserQuestionOptions(question) {
|
|
|
3337
3448
|
const host = $("#ai-question-options");
|
|
3338
3449
|
const customInput = $("#ai-question-custom-answer");
|
|
3339
3450
|
const isPending = question.status === "pending";
|
|
3451
|
+
const items = aiQuestionItems(question);
|
|
3452
|
+
aiQuestionDialogIndex = Math.min(Math.max(aiQuestionDialogIndex, 0), Math.max(0, items.length - 1));
|
|
3453
|
+
const item = items[aiQuestionDialogIndex] ?? items[0];
|
|
3454
|
+
const draft = isPending
|
|
3455
|
+
? (aiQuestionDrafts[aiQuestionDialogIndex] ?? { selectedOption: null, customAnswer: "" })
|
|
3456
|
+
: { selectedOption: item?.selectedOption ?? null, customAnswer: item?.customAnswer ?? "" };
|
|
3457
|
+
const answeredCount = isPending
|
|
3458
|
+
? aiQuestionDrafts.filter(aiQuestionDraftIsAnswered).length
|
|
3459
|
+
: items.filter((entry) => entry.answerText).length;
|
|
3460
|
+
const navigation = $("#ai-question-navigation");
|
|
3461
|
+
navigation.classList.toggle("hidden", items.length <= 1);
|
|
3462
|
+
$("#ai-question-progress").textContent = `问题 ${aiQuestionDialogIndex + 1} / ${items.length} · 已回答 ${answeredCount} / ${items.length}`;
|
|
3463
|
+
$("#ai-question-previous").disabled = aiQuestionDialogIndex <= 0;
|
|
3464
|
+
$("#ai-question-next").disabled = aiQuestionDialogIndex >= items.length - 1;
|
|
3465
|
+
$("#ai-question-text").textContent = item?.question ?? "";
|
|
3340
3466
|
host.replaceChildren();
|
|
3341
|
-
for (const option of
|
|
3467
|
+
for (const option of item?.options ?? []) {
|
|
3342
3468
|
const label = document.createElement("label");
|
|
3343
3469
|
label.className = "ai-question-option-item";
|
|
3344
3470
|
label.dataset.recommended = String(option.recommended === true);
|
|
@@ -3346,7 +3472,7 @@ function renderAiUserQuestionOptions(question) {
|
|
|
3346
3472
|
input.type = "radio";
|
|
3347
3473
|
input.name = "ai-question-choice";
|
|
3348
3474
|
input.value = String(option.index);
|
|
3349
|
-
input.checked =
|
|
3475
|
+
input.checked = draft.selectedOption === option.index;
|
|
3350
3476
|
input.disabled = !isPending;
|
|
3351
3477
|
const span = document.createElement("span");
|
|
3352
3478
|
span.textContent = `${option.recommended ? "(最推荐)" : ""}${option.label}`;
|
|
@@ -3359,29 +3485,30 @@ function renderAiUserQuestionOptions(question) {
|
|
|
3359
3485
|
customRadio.type = "radio";
|
|
3360
3486
|
customRadio.name = "ai-question-choice";
|
|
3361
3487
|
customRadio.value = "custom";
|
|
3362
|
-
customRadio.checked =
|
|
3488
|
+
customRadio.checked = draft.selectedOption == null && Boolean(String(draft.customAnswer ?? "").trim());
|
|
3363
3489
|
customRadio.disabled = !isPending;
|
|
3364
3490
|
const customText = document.createElement("span");
|
|
3365
3491
|
customText.textContent = "自定义回答";
|
|
3366
3492
|
customLabel.append(customRadio, customText);
|
|
3367
3493
|
host.append(customLabel);
|
|
3368
|
-
customInput.value =
|
|
3494
|
+
customInput.value = draft.customAnswer ?? "";
|
|
3369
3495
|
customInput.disabled = !isPending;
|
|
3370
3496
|
syncAiQuestionAnswerCount();
|
|
3371
3497
|
syncAiQuestionOptionPresentation();
|
|
3372
3498
|
// 提交按钮由选择状态驱动:待回答且已选择(或输入)时才可提交。
|
|
3373
3499
|
if (isPending) syncAiQuestionSubmitState();
|
|
3374
3500
|
else $("#ai-question-submit").disabled = true;
|
|
3501
|
+
$("#ai-question-submit").textContent = items.length > 1 ? "提交全部回答" : "提交回答";
|
|
3375
3502
|
$("#ai-question-skip").disabled = !isPending;
|
|
3376
3503
|
$("#ai-question-expiry").textContent = isPending
|
|
3377
|
-
?
|
|
3378
|
-
:
|
|
3504
|
+
? `请回答全部 ${items.length} 个问题后一次提交。有效期至 ${aiFormatDateTime(question.expiresAt)};过期未回答将自动失效,AI 不允许在未获得回答时自行假定答案。`
|
|
3505
|
+
: `该提问批次当前状态:${question.statusLabel}${item?.answerText ? ` · 当前回答:${item.answerText}` : ""}`;
|
|
3379
3506
|
}
|
|
3380
3507
|
|
|
3381
3508
|
async function refreshAiQuestionDialog() {
|
|
3382
3509
|
const question = await fetchAiUserQuestion(aiQuestionDialogQuestionId);
|
|
3383
3510
|
currentAiQuestionDialogView = question;
|
|
3384
|
-
|
|
3511
|
+
resetAiQuestionDialogState(question);
|
|
3385
3512
|
renderAiUserQuestionOptions(question);
|
|
3386
3513
|
return question;
|
|
3387
3514
|
}
|
|
@@ -3393,6 +3520,8 @@ async function openAiUserQuestionDialog(questionId) {
|
|
|
3393
3520
|
$("#ai-question-text").textContent = "";
|
|
3394
3521
|
$("#ai-question-options").replaceChildren();
|
|
3395
3522
|
$("#ai-question-custom-answer").value = "";
|
|
3523
|
+
$("#ai-question-navigation").classList.add("hidden");
|
|
3524
|
+
$("#ai-question-progress").textContent = "正在加载问题";
|
|
3396
3525
|
syncAiQuestionAnswerCount();
|
|
3397
3526
|
$("#ai-question-expiry").textContent = "正在加载问题……";
|
|
3398
3527
|
try {
|
|
@@ -3403,51 +3532,19 @@ async function openAiUserQuestionDialog(questionId) {
|
|
|
3403
3532
|
}
|
|
3404
3533
|
}
|
|
3405
3534
|
|
|
3406
|
-
function beginAiQuestionContinuationUi(conversationId
|
|
3535
|
+
function beginAiQuestionContinuationUi(conversationId) {
|
|
3407
3536
|
const tab = conversationId ? aiChatTabManager.findByConversation(conversationId) : null;
|
|
3408
3537
|
if (!tab) return null;
|
|
3409
|
-
const card = questionId
|
|
3410
|
-
? tab.feed.querySelector(`.ai-question-card[data-question-id="${CSS.escape(String(questionId))}"]`)
|
|
3411
|
-
: null;
|
|
3412
|
-
const note = card?.querySelector(".ai-interactive-note") ?? null;
|
|
3413
|
-
const status = card?.querySelector(".ai-status-chip") ?? null;
|
|
3414
|
-
const previousNote = note?.textContent ?? "";
|
|
3415
|
-
const previousStatus = status?.textContent ?? "";
|
|
3416
|
-
const controlStates = card
|
|
3417
|
-
? [...card.querySelectorAll("button")].map((button) => ({ button, disabled: button.disabled }))
|
|
3418
|
-
: [];
|
|
3419
|
-
if (card) {
|
|
3420
|
-
card.classList.add("is-resuming");
|
|
3421
|
-
card.setAttribute("aria-busy", "true");
|
|
3422
|
-
controlStates.forEach(({ button }) => { button.disabled = true; });
|
|
3423
|
-
if (status) status.textContent = "处理中";
|
|
3424
|
-
if (note) note.textContent = "回答已作为工具结果提交,正在根据你的回答继续处理…";
|
|
3425
|
-
}
|
|
3426
3538
|
aiQuestionContinuationTabIds.add(tab.id);
|
|
3427
3539
|
setAiChatTabStatus(tab, "streaming");
|
|
3428
3540
|
if (isActiveAiChatTab(tab)) syncAiRequestControls();
|
|
3429
3541
|
scrollAiFeedToBottom(tab.feed);
|
|
3430
|
-
return {
|
|
3431
|
-
tab,
|
|
3432
|
-
card,
|
|
3433
|
-
note,
|
|
3434
|
-
status,
|
|
3435
|
-
previousNote,
|
|
3436
|
-
previousStatus,
|
|
3437
|
-
controlStates
|
|
3438
|
-
};
|
|
3542
|
+
return { tab };
|
|
3439
3543
|
}
|
|
3440
3544
|
|
|
3441
3545
|
function finishAiQuestionContinuationUi(continuationUi, failed = false) {
|
|
3442
3546
|
if (!continuationUi) return;
|
|
3443
3547
|
aiQuestionContinuationTabIds.delete(continuationUi.tab.id);
|
|
3444
|
-
if (continuationUi.card?.isConnected) {
|
|
3445
|
-
continuationUi.card.classList.remove("is-resuming");
|
|
3446
|
-
continuationUi.card.removeAttribute("aria-busy");
|
|
3447
|
-
continuationUi.controlStates.forEach(({ button, disabled }) => { button.disabled = disabled; });
|
|
3448
|
-
if (continuationUi.note) continuationUi.note.textContent = continuationUi.previousNote;
|
|
3449
|
-
if (continuationUi.status) continuationUi.status.textContent = continuationUi.previousStatus;
|
|
3450
|
-
}
|
|
3451
3548
|
if (aiChatTabManager.get(continuationUi.tab.id)) setAiChatTabStatus(continuationUi.tab, failed ? "error" : "ready");
|
|
3452
3549
|
if (isActiveAiChatTab(continuationUi.tab)) syncAiRequestControls();
|
|
3453
3550
|
}
|
|
@@ -3480,26 +3577,21 @@ async function respondAiUserQuestion(questionId, payload) {
|
|
|
3480
3577
|
const conversationId = typeof knownQuestion?.conversationId === "string" ? knownQuestion.conversationId : null;
|
|
3481
3578
|
if (questionDialog.open) questionDialog.close();
|
|
3482
3579
|
if (approvalCenterDialog.open) approvalCenterDialog.close();
|
|
3483
|
-
continuationUi = beginAiQuestionContinuationUi(conversationId
|
|
3580
|
+
continuationUi = beginAiQuestionContinuationUi(conversationId);
|
|
3484
3581
|
let question;
|
|
3485
3582
|
if (payload.action === "reject") {
|
|
3486
3583
|
question = await api(questionsEndpoint(`/${encodeURIComponent(String(questionId))}/reject`), { method: "POST" });
|
|
3487
|
-
} else if (payload.action === "custom") {
|
|
3488
|
-
question = await api(questionsEndpoint(`/${encodeURIComponent(String(questionId))}/answer`), { method: "POST", body: { customAnswer: payload.customAnswer } });
|
|
3489
3584
|
} else {
|
|
3490
3585
|
question = await api(questionsEndpoint(`/${encodeURIComponent(String(questionId))}/answer`), {
|
|
3491
3586
|
method: "POST",
|
|
3492
|
-
body: {
|
|
3493
|
-
selectedOption: payload.selectedOption,
|
|
3494
|
-
...(payload.customAnswer ? { customAnswer: payload.customAnswer } : {})
|
|
3495
|
-
}
|
|
3587
|
+
body: { answers: payload.answers }
|
|
3496
3588
|
});
|
|
3497
3589
|
}
|
|
3498
3590
|
cacheAiQuestionView(question);
|
|
3499
3591
|
currentAiQuestionDialogView = question;
|
|
3500
3592
|
aiQuestionDialogQuestionId = String(question.id);
|
|
3501
3593
|
await reloadAiQuestionConversation(question.conversationId ?? conversationId);
|
|
3502
|
-
toast(payload.action === "reject" ? "
|
|
3594
|
+
toast(payload.action === "reject" ? "已跳过该提问批次,AI 已继续处理" : "全部回答已提交,AI 已继续处理");
|
|
3503
3595
|
return question;
|
|
3504
3596
|
} catch (error) {
|
|
3505
3597
|
continuationFailed = true;
|
|
@@ -3735,6 +3827,30 @@ async function favoriteAiConversation(conversation) {
|
|
|
3735
3827
|
toast(updated.isFavorite ? "对话已收藏" : "已取消收藏");
|
|
3736
3828
|
}
|
|
3737
3829
|
|
|
3830
|
+
async function renameAiConversation(conversation) {
|
|
3831
|
+
if (!conversation?.id) return;
|
|
3832
|
+
const title = await inputToast("输入新的对话名称", {
|
|
3833
|
+
title: "重命名对话",
|
|
3834
|
+
inputLabel: "对话名称",
|
|
3835
|
+
value: conversation.title || "新对话",
|
|
3836
|
+
placeholder: "对话名称",
|
|
3837
|
+
confirmLabel: "保存",
|
|
3838
|
+
maxLength: 200
|
|
3839
|
+
});
|
|
3840
|
+
if (title === null) return;
|
|
3841
|
+
if (!title) {
|
|
3842
|
+
toast("对话名称不能为空", "error");
|
|
3843
|
+
return;
|
|
3844
|
+
}
|
|
3845
|
+
const updated = await api(`/api/ai-conversations/${encodeURIComponent(conversation.id)}/title`, {
|
|
3846
|
+
method: "PATCH",
|
|
3847
|
+
body: { title }
|
|
3848
|
+
});
|
|
3849
|
+
upsertAiConversationSummary(updated);
|
|
3850
|
+
applyAiConversationTitle(updated.title, updated.id);
|
|
3851
|
+
toast("对话已重命名");
|
|
3852
|
+
}
|
|
3853
|
+
|
|
3738
3854
|
async function deleteAiConversation(conversation) {
|
|
3739
3855
|
if (conversation.isFavorite === true) {
|
|
3740
3856
|
toast("收藏的对话不能清理,请先取消收藏", "error");
|
|
@@ -5389,8 +5505,11 @@ function showLineCitationMenu(event, lineIndex) {
|
|
|
5389
5505
|
selectChapterLines(lineIndex, lineIndex);
|
|
5390
5506
|
}
|
|
5391
5507
|
const menu = $("#line-citation-menu");
|
|
5392
|
-
|
|
5393
|
-
|
|
5508
|
+
const canAddComment = canWritePermissionModule(state.work, "comments");
|
|
5509
|
+
const canAddTodo = canWritePermissionModule(state.work, "todos");
|
|
5510
|
+
$("#add-line-annotation").classList.toggle("hidden", !canAddComment);
|
|
5511
|
+
$("#add-line-todo").classList.toggle("hidden", !canAddTodo);
|
|
5512
|
+
$("#line-citation-annotation-separator").classList.toggle("hidden", !(canAddComment || canAddTodo));
|
|
5394
5513
|
const { start, end } = chapterLineSelection;
|
|
5395
5514
|
$("#line-citation-label").textContent = start === end ? `第 ${start + 1} 行` : `第 ${start + 1}-${end + 1} 行`;
|
|
5396
5515
|
menu.classList.remove("hidden");
|
|
@@ -6767,6 +6886,7 @@ async function loadWorks(preferredId) {
|
|
|
6767
6886
|
}
|
|
6768
6887
|
|
|
6769
6888
|
function restoredSettingsReturnContext(route) {
|
|
6889
|
+
if (route.returnView === "im") return { view: "im" };
|
|
6770
6890
|
if (route.returnView === "module" && route.returnModule) return { view: "module", module: route.returnModule };
|
|
6771
6891
|
if (route.returnView === "editor" && route.returnChapterId) return { view: "editor", chapterId: route.returnChapterId };
|
|
6772
6892
|
if (route.returnView === "welcome") return { view: "welcome" };
|
|
@@ -6811,6 +6931,10 @@ async function initializePage() {
|
|
|
6811
6931
|
showShelf();
|
|
6812
6932
|
return;
|
|
6813
6933
|
}
|
|
6934
|
+
if (route.view === "im") {
|
|
6935
|
+
await imWorkspace.open();
|
|
6936
|
+
return;
|
|
6937
|
+
}
|
|
6814
6938
|
|
|
6815
6939
|
const requestedWork = route.workId ? state.works.find((work) => work.id === route.workId) : null;
|
|
6816
6940
|
if (route.workId && !requestedWork) {
|
|
@@ -6892,6 +7016,7 @@ async function initializePage() {
|
|
|
6892
7016
|
}
|
|
6893
7017
|
|
|
6894
7018
|
function showShelf() {
|
|
7019
|
+
imWorkspace.close();
|
|
6895
7020
|
stopBackgroundTaskCenter();
|
|
6896
7021
|
dismissChapterInsightToast();
|
|
6897
7022
|
dismissDeleteToasts();
|
|
@@ -6916,6 +7041,7 @@ function showShelf() {
|
|
|
6916
7041
|
}
|
|
6917
7042
|
|
|
6918
7043
|
function captureSettingsReturnContext() {
|
|
7044
|
+
if (imWorkspace.opened) return { view: "im" };
|
|
6919
7045
|
if (!$("#shelf-view").classList.contains("hidden")) return { view: "shelf" };
|
|
6920
7046
|
if (!$("#editor-view").classList.contains("hidden")) return { view: "editor", chapterId: state.chapter?.id ?? null };
|
|
6921
7047
|
if (!$("#module-view").classList.contains("hidden")) return { view: "module", module: state.module };
|
|
@@ -6943,7 +7069,9 @@ function renderSettingsHub() {
|
|
|
6943
7069
|
$("#top-search-button").disabled = !canReadAggregate;
|
|
6944
7070
|
$("#export-button").disabled = !canExportManuscript;
|
|
6945
7071
|
$("#export-button").setAttribute("aria-expanded", "false");
|
|
6946
|
-
$("#settings-return").textContent = settingsReturnContext?.view === "
|
|
7072
|
+
$("#settings-return").textContent = settingsReturnContext?.view === "im"
|
|
7073
|
+
? "返回 IM"
|
|
7074
|
+
: settingsReturnContext?.view === "shelf" || !hasWork ? "返回书架" : "返回当前作品";
|
|
6947
7075
|
$("#settings-work-note").textContent = hasWork
|
|
6948
7076
|
? `当前作品:《${state.work.title}》。导出正文时可选择 Markdown ZIP 或 DOCX;DOCX 在有封面时会嵌入为首页。`
|
|
6949
7077
|
: "当前未选择作品;打开作品后可使用导出。";
|
|
@@ -8225,6 +8353,7 @@ async function showSettingsHub() {
|
|
|
8225
8353
|
settingsReturnContext = captureSettingsReturnContext();
|
|
8226
8354
|
state.dirty = false;
|
|
8227
8355
|
}
|
|
8356
|
+
imWorkspace.close();
|
|
8228
8357
|
dismissChapterInsightToast();
|
|
8229
8358
|
dismissTokenUsageDetails({ restoreFocus: false });
|
|
8230
8359
|
dismissDeleteToasts();
|
|
@@ -8261,6 +8390,7 @@ async function returnFromSettings() {
|
|
|
8261
8390
|
$("#platform-ai-view").classList.add("hidden");
|
|
8262
8391
|
$("#platform-usage-view").classList.add("hidden");
|
|
8263
8392
|
$("#work-audit-view").classList.add("hidden");
|
|
8393
|
+
if (context.view === "im") return imWorkspace.open();
|
|
8264
8394
|
if (context.view === "shelf" || !state.work) return showShelf();
|
|
8265
8395
|
$("#app").classList.remove("shelf-mode");
|
|
8266
8396
|
$("#shelf-view").classList.add("hidden");
|
|
@@ -8410,6 +8540,7 @@ function resetWorkScopedUiCaches() {
|
|
|
8410
8540
|
}
|
|
8411
8541
|
|
|
8412
8542
|
async function selectWork(workId, preferredChapterId = null) {
|
|
8543
|
+
imWorkspace.close();
|
|
8413
8544
|
chapterSelectionRequestGeneration += 1;
|
|
8414
8545
|
const discarding = state.work?.id !== workId && state.dirty;
|
|
8415
8546
|
if (discarding && !(await confirmDiscardChanges())) return false;
|
|
@@ -8533,7 +8664,7 @@ function renderTree() {
|
|
|
8533
8664
|
if (!state.work) return;
|
|
8534
8665
|
const count = state.work.volumes.reduce((total, volume) => total + Number(volume.chapterCount ?? volume.chapters?.length ?? 0), 0);
|
|
8535
8666
|
const proseEditable = canEditProse();
|
|
8536
|
-
$("#chapter-count").textContent =
|
|
8667
|
+
$("#chapter-count").querySelector(".chapter-count-number").textContent = String(count);
|
|
8537
8668
|
$("#reader-open-button").disabled = !canReadModule("editor") || count === 0;
|
|
8538
8669
|
$("#novel-tree").classList.remove("empty-copy");
|
|
8539
8670
|
$("#novel-tree").innerHTML = state.work.volumes.map((volume) => {
|
|
@@ -8554,7 +8685,7 @@ function renderTree() {
|
|
|
8554
8685
|
return `
|
|
8555
8686
|
<div class="volume-node ${collapsed ? "is-collapsed" : ""}" data-volume-id="${esc(volume.id)}">
|
|
8556
8687
|
<div class="volume-title">
|
|
8557
|
-
<button class="volume-toggle" type="button" data-volume-toggle="${esc(volume.id)}" aria-expanded="${collapsed ? "false" : "true"}" title="左键展开或折叠;右键打开分卷详情;可将章节拖到这里追加"><span>${esc(volume.title)}</span><span>${Number(volume.chapterCount ?? chapters.length)} 章</span></button>
|
|
8688
|
+
<button class="volume-toggle" type="button" data-volume-toggle="${esc(volume.id)}" aria-expanded="${collapsed ? "false" : "true"}" title="左键展开或折叠;右键打开分卷详情;可将章节拖到这里追加"><span>${esc(volume.title)}</span><span class="volume-chapter-count"><span class="volume-chapter-count-number">${Number(volume.chapterCount ?? chapters.length)}</span><span class="volume-chapter-count-unit"> 章</span></span></button>
|
|
8558
8689
|
${proseEditable ? `<button class="ghost-button volume-detail-button" type="button" data-volume-detail="${esc(volume.id)}" aria-label="打开“${esc(volume.title)}”分卷详情" title="打开“${esc(volume.title)}”分卷详情"><svg class="volume-detail-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5"></circle><path d="M12 10.5v5.5M12 7.5h.01"></path></svg></button>` : ""}
|
|
8559
8690
|
${proseEditable ? `<button class="add-button chapter-add-button" type="button" data-new-chapter-volume="${esc(volume.id)}" aria-label="在“${esc(volume.title)}”中新建章节" title="在“${esc(volume.title)}”中新建章节">+</button>` : ""}
|
|
8560
8691
|
</div>
|
|
@@ -9103,6 +9234,7 @@ async function resolveCurrentChapterForeshadowReminder() {
|
|
|
9103
9234
|
}
|
|
9104
9235
|
|
|
9105
9236
|
async function selectChapter(chapterId, { editMode = false } = {}) {
|
|
9237
|
+
imWorkspace.close();
|
|
9106
9238
|
const workId = state.work?.id;
|
|
9107
9239
|
if (!workId) return false;
|
|
9108
9240
|
const selectionGeneration = ++chapterSelectionRequestGeneration;
|
|
@@ -9663,6 +9795,7 @@ function tidyChapterBlankLines() {
|
|
|
9663
9795
|
}
|
|
9664
9796
|
|
|
9665
9797
|
function showWelcome(hasWork = false) {
|
|
9798
|
+
imWorkspace.close();
|
|
9666
9799
|
chapterSelectionRequestId += 1;
|
|
9667
9800
|
clearChapterForeshadowReminders({ invalidateRequest: true });
|
|
9668
9801
|
dismissChapterInsightToast();
|
|
@@ -9696,6 +9829,7 @@ const moduleMeta = {
|
|
|
9696
9829
|
};
|
|
9697
9830
|
|
|
9698
9831
|
async function showModule(module) {
|
|
9832
|
+
imWorkspace.close();
|
|
9699
9833
|
if (!state.work) return showWelcome();
|
|
9700
9834
|
if (!canReadModule(module)) {
|
|
9701
9835
|
const fallback = firstReadableUiModule(state.work);
|
|
@@ -17909,6 +18043,7 @@ async function sendAiWithOptions({ ignoreContextWarning = false, retry = null }
|
|
|
17909
18043
|
}
|
|
17910
18044
|
setAiChatTabStatus(tab, "streaming");
|
|
17911
18045
|
syncAiRequestControls();
|
|
18046
|
+
scrollAiFeedToBottom(tab.feed, { force: true });
|
|
17912
18047
|
try {
|
|
17913
18048
|
try {
|
|
17914
18049
|
await ensureAiModelsLoaded();
|
|
@@ -18507,8 +18642,21 @@ function appendMessage(role, text, citations = [], createdAt = null, metadata =
|
|
|
18507
18642
|
const references = document.createElement("div");
|
|
18508
18643
|
references.className = "message-citations";
|
|
18509
18644
|
for (const citation of citations) {
|
|
18510
|
-
const reference = document.createElement("
|
|
18511
|
-
reference.
|
|
18645
|
+
const reference = document.createElement("button");
|
|
18646
|
+
reference.type = "button";
|
|
18647
|
+
reference.className = "message-citation";
|
|
18648
|
+
reference.setAttribute("aria-haspopup", "dialog");
|
|
18649
|
+
reference.setAttribute("aria-expanded", "false");
|
|
18650
|
+
reference.setAttribute("aria-controls", "ai-citation-popover");
|
|
18651
|
+
reference.setAttribute("aria-label", `${aiCitationTagLabel(citation)},点击查看引用正文`);
|
|
18652
|
+
const referenceLabel = document.createElement("span");
|
|
18653
|
+
referenceLabel.textContent = aiCitationTagLabel(citation);
|
|
18654
|
+
reference.append(referenceLabel);
|
|
18655
|
+
reference.addEventListener("click", () => {
|
|
18656
|
+
const popover = $("#ai-citation-popover");
|
|
18657
|
+
if (aiCitationPopoverTrigger === reference && popover && !popover.classList.contains("hidden")) closeAiCitationPopover();
|
|
18658
|
+
else openAiCitationPopover(citation, reference);
|
|
18659
|
+
});
|
|
18512
18660
|
references.append(reference);
|
|
18513
18661
|
}
|
|
18514
18662
|
message.append(references);
|
|
@@ -20727,6 +20875,7 @@ document.addEventListener("pointerdown", (event) => {
|
|
|
20727
20875
|
if (!event.target.closest(".prompt-composer")) hideAiMentionMenu();
|
|
20728
20876
|
if (!event.target.closest("#ai-context-meter") && !event.target.closest("#ai-context-popover")) setAiContextDistributionVisible(false);
|
|
20729
20877
|
if (!event.target.closest("#ai-model-picker") && !event.target.closest("#ai-model-popover")) setAiModelPickerVisible(false);
|
|
20878
|
+
if (!event.target.closest("#ai-citation-popover") && !event.target.closest(".message-citation")) closeAiCitationPopover();
|
|
20730
20879
|
if (!event.target.closest("#account-button") && !event.target.closest("#account-menu")) {
|
|
20731
20880
|
$("#account-menu").classList.add("hidden");
|
|
20732
20881
|
$("#account-button").setAttribute("aria-expanded", "false");
|
|
@@ -20753,6 +20902,10 @@ document.addEventListener("keydown", (event) => {
|
|
|
20753
20902
|
setAiModelPickerVisible(false);
|
|
20754
20903
|
return;
|
|
20755
20904
|
}
|
|
20905
|
+
if (!$("#ai-citation-popover").classList.contains("hidden")) {
|
|
20906
|
+
closeAiCitationPopover({ restoreFocus: true });
|
|
20907
|
+
return;
|
|
20908
|
+
}
|
|
20756
20909
|
if (!$("#chapter-search-panel").classList.contains("hidden")) {
|
|
20757
20910
|
closeChapterSearchPanel();
|
|
20758
20911
|
return;
|
|
@@ -20776,6 +20929,13 @@ document.addEventListener("keydown", (event) => {
|
|
|
20776
20929
|
setAiContextDistributionVisible(false);
|
|
20777
20930
|
}
|
|
20778
20931
|
});
|
|
20932
|
+
document.addEventListener("keydown", (event) => {
|
|
20933
|
+
if (!isSaveShortcut(event, navigator.platform) || !state.chapter || state.module !== "editor" || chapterEditorReadOnly || !canEditProse()) return;
|
|
20934
|
+
event.preventDefault();
|
|
20935
|
+
event.stopPropagation();
|
|
20936
|
+
if (event.repeat) return;
|
|
20937
|
+
$("#save-button").click();
|
|
20938
|
+
}, { capture: true });
|
|
20779
20939
|
document.addEventListener("keydown", (event) => {
|
|
20780
20940
|
if (!isGlobalSearchShortcut(event) || !state.work) return;
|
|
20781
20941
|
event.preventDefault();
|
|
@@ -20792,6 +20952,7 @@ $("#ai-context-meter").addEventListener("click", () => {
|
|
|
20792
20952
|
setAiContextDistributionVisible($("#ai-context-popover").classList.contains("hidden"));
|
|
20793
20953
|
});
|
|
20794
20954
|
$("#ai-context-popover-close").addEventListener("click", () => setAiContextDistributionVisible(false));
|
|
20955
|
+
$("#ai-citation-popover-close").addEventListener("click", () => closeAiCitationPopover({ restoreFocus: true }));
|
|
20795
20956
|
$("#ai-send").addEventListener("click", activateAiSendControl);
|
|
20796
20957
|
$("#ai-conversation-switcher").addEventListener("click", () => {
|
|
20797
20958
|
setAiConversationSwitcherVisible($("#ai-conversation-switcher-menu").classList.contains("hidden"));
|
|
@@ -20936,6 +21097,19 @@ $("#ai-history-action-menu").addEventListener("click", async (event) => {
|
|
|
20936
21097
|
}
|
|
20937
21098
|
return;
|
|
20938
21099
|
}
|
|
21100
|
+
if (action === "rename") {
|
|
21101
|
+
option.disabled = true;
|
|
21102
|
+
try {
|
|
21103
|
+
await renameAiConversation(conversation);
|
|
21104
|
+
closeAiHistoryActionMenu(true);
|
|
21105
|
+
} catch (error) {
|
|
21106
|
+
toast(`对话重命名失败:${error.message}`, "error");
|
|
21107
|
+
option.focus();
|
|
21108
|
+
} finally {
|
|
21109
|
+
option.disabled = false;
|
|
21110
|
+
}
|
|
21111
|
+
return;
|
|
21112
|
+
}
|
|
20939
21113
|
if (action === "copy-session-id") {
|
|
20940
21114
|
option.disabled = true;
|
|
20941
21115
|
try {
|
|
@@ -21033,14 +21207,17 @@ $("#ai-write-plan-undo").addEventListener("click", () => {
|
|
|
21033
21207
|
$("#ai-question-close").addEventListener("click", () => $("#ai-question-dialog").close());
|
|
21034
21208
|
|
|
21035
21209
|
function syncAiQuestionSubmitState() {
|
|
21036
|
-
const checked = document.querySelector('input[name="ai-question-choice"]:checked');
|
|
21037
|
-
const customInput = $("#ai-question-custom-answer");
|
|
21038
21210
|
const submit = $("#ai-question-submit");
|
|
21039
|
-
const
|
|
21040
|
-
|
|
21041
|
-
|
|
21211
|
+
const questionCount = aiQuestionItems(currentAiQuestionDialogView).length;
|
|
21212
|
+
const answeredCount = aiQuestionDrafts.filter(aiQuestionDraftIsAnswered).length;
|
|
21213
|
+
const unansweredCount = Math.max(0, questionCount - answeredCount);
|
|
21214
|
+
$("#ai-question-progress").textContent = `问题 ${aiQuestionDialogIndex + 1} / ${questionCount} · 已回答 ${answeredCount} / ${questionCount}`;
|
|
21215
|
+
const disabled = aiQuestionDialogBusy
|
|
21216
|
+
|| currentAiQuestionDialogView?.status !== "pending"
|
|
21217
|
+
|| aiQuestionDrafts.length !== questionCount
|
|
21218
|
+
|| unansweredCount > 0;
|
|
21042
21219
|
submit.disabled = disabled;
|
|
21043
|
-
submit.title =
|
|
21220
|
+
submit.title = unansweredCount > 0 ? `还有 ${unansweredCount} 个问题未回答` : "";
|
|
21044
21221
|
}
|
|
21045
21222
|
$("#ai-question-form").addEventListener("change", (event) => {
|
|
21046
21223
|
const control = event.target;
|
|
@@ -21048,6 +21225,7 @@ $("#ai-question-form").addEventListener("change", (event) => {
|
|
|
21048
21225
|
const isCustom = control.value === "custom";
|
|
21049
21226
|
const customInput = $("#ai-question-custom-answer");
|
|
21050
21227
|
if (isCustom && !control.disabled) customInput.focus();
|
|
21228
|
+
updateCurrentAiQuestionDraft();
|
|
21051
21229
|
syncAiQuestionOptionPresentation();
|
|
21052
21230
|
syncAiQuestionSubmitState();
|
|
21053
21231
|
});
|
|
@@ -21059,23 +21237,30 @@ $("#ai-question-custom-answer").addEventListener("input", () => {
|
|
|
21059
21237
|
const customRadio = document.querySelector('input[name="ai-question-choice"][value="custom"]');
|
|
21060
21238
|
if (customRadio && !customRadio.disabled) customRadio.checked = true;
|
|
21061
21239
|
}
|
|
21240
|
+
updateCurrentAiQuestionDraft();
|
|
21062
21241
|
syncAiQuestionOptionPresentation();
|
|
21063
21242
|
syncAiQuestionSubmitState();
|
|
21064
21243
|
});
|
|
21244
|
+
function moveAiQuestionDialog(direction) {
|
|
21245
|
+
if (!currentAiQuestionDialogView) return;
|
|
21246
|
+
updateCurrentAiQuestionDraft();
|
|
21247
|
+
const maximumIndex = Math.max(0, aiQuestionItems(currentAiQuestionDialogView).length - 1);
|
|
21248
|
+
aiQuestionDialogIndex = Math.min(maximumIndex, Math.max(0, aiQuestionDialogIndex + direction));
|
|
21249
|
+
renderAiUserQuestionOptions(currentAiQuestionDialogView);
|
|
21250
|
+
$("#ai-question-text").focus();
|
|
21251
|
+
}
|
|
21252
|
+
$("#ai-question-previous").addEventListener("click", () => moveAiQuestionDialog(-1));
|
|
21253
|
+
$("#ai-question-next").addEventListener("click", () => moveAiQuestionDialog(1));
|
|
21065
21254
|
$("#ai-question-submit").addEventListener("click", () => {
|
|
21066
|
-
|
|
21067
|
-
|
|
21068
|
-
if (
|
|
21069
|
-
const text = String($("#ai-question-custom-answer").value).trim();
|
|
21070
|
-
if (!text) return;
|
|
21071
|
-
respondAiUserQuestion(aiQuestionDialogQuestionId, { action: "custom", customAnswer: text }).catch((error) => toast(error.message, "error"));
|
|
21072
|
-
return;
|
|
21073
|
-
}
|
|
21074
|
-
const supplementalAnswer = String($("#ai-question-custom-answer").value).trim();
|
|
21255
|
+
if (aiQuestionDialogQuestionId == null) return;
|
|
21256
|
+
updateCurrentAiQuestionDraft();
|
|
21257
|
+
if (!aiQuestionDrafts.every(aiQuestionDraftIsAnswered)) return syncAiQuestionSubmitState();
|
|
21075
21258
|
respondAiUserQuestion(aiQuestionDialogQuestionId, {
|
|
21076
|
-
action: "
|
|
21077
|
-
|
|
21078
|
-
|
|
21259
|
+
action: "answer",
|
|
21260
|
+
answers: aiQuestionDrafts.map((draft) => ({
|
|
21261
|
+
...(Number.isInteger(draft.selectedOption) ? { selectedOption: draft.selectedOption } : {}),
|
|
21262
|
+
...(String(draft.customAnswer ?? "").trim() ? { customAnswer: String(draft.customAnswer).trim() } : {})
|
|
21263
|
+
}))
|
|
21079
21264
|
}).catch((error) => toast(error.message, "error"));
|
|
21080
21265
|
});
|
|
21081
21266
|
$("#ai-question-skip").addEventListener("click", () => {
|
|
@@ -21224,6 +21409,7 @@ window.addEventListener("online", () => {
|
|
|
21224
21409
|
});
|
|
21225
21410
|
window.addEventListener("offline", () => updateSystemHealth({ status: "offline" }));
|
|
21226
21411
|
window.addEventListener("resize", () => {
|
|
21412
|
+
if (aiCitationPopoverTrigger && !$("#ai-citation-popover").classList.contains("hidden")) positionAiCitationPopover(aiCitationPopoverTrigger);
|
|
21227
21413
|
if ($("#reader-view").classList.contains("hidden") || readingPreferences.mode !== "paged") return;
|
|
21228
21414
|
if (readingResizeFrame !== null) window.cancelAnimationFrame(readingResizeFrame);
|
|
21229
21415
|
readingResizeFrame = window.requestAnimationFrame(() => {
|
|
@@ -21232,7 +21418,25 @@ window.addEventListener("resize", () => {
|
|
|
21232
21418
|
});
|
|
21233
21419
|
});
|
|
21234
21420
|
|
|
21421
|
+
const imWorkspace = createImWorkspace({
|
|
21422
|
+
api,
|
|
21423
|
+
esc,
|
|
21424
|
+
renderMarkdown,
|
|
21425
|
+
toast,
|
|
21426
|
+
confirmToast,
|
|
21427
|
+
state,
|
|
21428
|
+
showShelf,
|
|
21429
|
+
onRouteChange: schedulePresenceHeartbeat,
|
|
21430
|
+
beforeOpen: async () => {
|
|
21431
|
+
if (state.dirty && !(await confirmDiscardChanges("当前章节有未保存修改,进入 IM 将放弃本地修改。是否继续?"))) return false;
|
|
21432
|
+
cancelChapterAutoSave();
|
|
21433
|
+
state.dirty = false;
|
|
21434
|
+
return true;
|
|
21435
|
+
}
|
|
21436
|
+
});
|
|
21437
|
+
|
|
21235
21438
|
initializeAiChatTabs();
|
|
21439
|
+
imWorkspace.start();
|
|
21236
21440
|
initializePage().catch((error) => {
|
|
21237
21441
|
restoringPageRoute = false;
|
|
21238
21442
|
if (state.user) {
|
|
@@ -21241,4 +21445,6 @@ initializePage().catch((error) => {
|
|
|
21241
21445
|
}
|
|
21242
21446
|
showShelf();
|
|
21243
21447
|
toast(`系统初始化失败:${error.message}`, "error");
|
|
21448
|
+
}).finally(() => {
|
|
21449
|
+
if (state.user) void imWorkspace.activate().catch((error) => toast(`IM 初始化失败:${error.message}`, "error"));
|
|
21244
21450
|
});
|