@musnows/scriverse 0.9.9 → 1.0.0
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 +279 -82
- 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/page-route.d.ts +2 -1
- package/dist/public/page-route.js +4 -1
- package/dist/public/styles.css +335 -3
- 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/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,
|
|
@@ -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();
|
|
@@ -3122,7 +3189,7 @@ function openAiToolCallDetail(toolCall) {
|
|
|
3122
3189
|
|
|
3123
3190
|
function createAiToolCallButton(toolCall) {
|
|
3124
3191
|
const name = String(toolCall?.name ?? "unknown");
|
|
3125
|
-
if (name === "propose_write_plan"
|
|
3192
|
+
if (name === "propose_write_plan") {
|
|
3126
3193
|
const card = createInteractiveToolCard(toolCall, AI_TOOL_CARD_ACTIONS);
|
|
3127
3194
|
if (card) return card;
|
|
3128
3195
|
}
|
|
@@ -3147,6 +3214,8 @@ let currentPlanDialogFocusConfirm = false;
|
|
|
3147
3214
|
let aiQuestionDialogQuestionId = null;
|
|
3148
3215
|
let currentAiQuestionDialogView = null;
|
|
3149
3216
|
let aiQuestionDialogBusy = false;
|
|
3217
|
+
let aiQuestionDialogIndex = 0;
|
|
3218
|
+
let aiQuestionDrafts = [];
|
|
3150
3219
|
const aiQuestionContinuationTabIds = new Set();
|
|
3151
3220
|
let autoOpenedQuestionIds = new Set();
|
|
3152
3221
|
const aiApprovalCenterState = { status: "" };
|
|
@@ -3318,6 +3387,47 @@ async function fetchAiUserQuestion(questionId) {
|
|
|
3318
3387
|
return question;
|
|
3319
3388
|
}
|
|
3320
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
|
+
|
|
3321
3431
|
function syncAiQuestionOptionPresentation() {
|
|
3322
3432
|
const checked = document.querySelector('input[name="ai-question-choice"]:checked');
|
|
3323
3433
|
for (const label of document.querySelectorAll(".ai-question-option-item")) {
|
|
@@ -3338,8 +3448,23 @@ function renderAiUserQuestionOptions(question) {
|
|
|
3338
3448
|
const host = $("#ai-question-options");
|
|
3339
3449
|
const customInput = $("#ai-question-custom-answer");
|
|
3340
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 ?? "";
|
|
3341
3466
|
host.replaceChildren();
|
|
3342
|
-
for (const option of
|
|
3467
|
+
for (const option of item?.options ?? []) {
|
|
3343
3468
|
const label = document.createElement("label");
|
|
3344
3469
|
label.className = "ai-question-option-item";
|
|
3345
3470
|
label.dataset.recommended = String(option.recommended === true);
|
|
@@ -3347,7 +3472,7 @@ function renderAiUserQuestionOptions(question) {
|
|
|
3347
3472
|
input.type = "radio";
|
|
3348
3473
|
input.name = "ai-question-choice";
|
|
3349
3474
|
input.value = String(option.index);
|
|
3350
|
-
input.checked =
|
|
3475
|
+
input.checked = draft.selectedOption === option.index;
|
|
3351
3476
|
input.disabled = !isPending;
|
|
3352
3477
|
const span = document.createElement("span");
|
|
3353
3478
|
span.textContent = `${option.recommended ? "(最推荐)" : ""}${option.label}`;
|
|
@@ -3360,29 +3485,30 @@ function renderAiUserQuestionOptions(question) {
|
|
|
3360
3485
|
customRadio.type = "radio";
|
|
3361
3486
|
customRadio.name = "ai-question-choice";
|
|
3362
3487
|
customRadio.value = "custom";
|
|
3363
|
-
customRadio.checked =
|
|
3488
|
+
customRadio.checked = draft.selectedOption == null && Boolean(String(draft.customAnswer ?? "").trim());
|
|
3364
3489
|
customRadio.disabled = !isPending;
|
|
3365
3490
|
const customText = document.createElement("span");
|
|
3366
3491
|
customText.textContent = "自定义回答";
|
|
3367
3492
|
customLabel.append(customRadio, customText);
|
|
3368
3493
|
host.append(customLabel);
|
|
3369
|
-
customInput.value =
|
|
3494
|
+
customInput.value = draft.customAnswer ?? "";
|
|
3370
3495
|
customInput.disabled = !isPending;
|
|
3371
3496
|
syncAiQuestionAnswerCount();
|
|
3372
3497
|
syncAiQuestionOptionPresentation();
|
|
3373
3498
|
// 提交按钮由选择状态驱动:待回答且已选择(或输入)时才可提交。
|
|
3374
3499
|
if (isPending) syncAiQuestionSubmitState();
|
|
3375
3500
|
else $("#ai-question-submit").disabled = true;
|
|
3501
|
+
$("#ai-question-submit").textContent = items.length > 1 ? "提交全部回答" : "提交回答";
|
|
3376
3502
|
$("#ai-question-skip").disabled = !isPending;
|
|
3377
3503
|
$("#ai-question-expiry").textContent = isPending
|
|
3378
|
-
?
|
|
3379
|
-
:
|
|
3504
|
+
? `请回答全部 ${items.length} 个问题后一次提交。有效期至 ${aiFormatDateTime(question.expiresAt)};过期未回答将自动失效,AI 不允许在未获得回答时自行假定答案。`
|
|
3505
|
+
: `该提问批次当前状态:${question.statusLabel}${item?.answerText ? ` · 当前回答:${item.answerText}` : ""}`;
|
|
3380
3506
|
}
|
|
3381
3507
|
|
|
3382
3508
|
async function refreshAiQuestionDialog() {
|
|
3383
3509
|
const question = await fetchAiUserQuestion(aiQuestionDialogQuestionId);
|
|
3384
3510
|
currentAiQuestionDialogView = question;
|
|
3385
|
-
|
|
3511
|
+
resetAiQuestionDialogState(question);
|
|
3386
3512
|
renderAiUserQuestionOptions(question);
|
|
3387
3513
|
return question;
|
|
3388
3514
|
}
|
|
@@ -3394,6 +3520,8 @@ async function openAiUserQuestionDialog(questionId) {
|
|
|
3394
3520
|
$("#ai-question-text").textContent = "";
|
|
3395
3521
|
$("#ai-question-options").replaceChildren();
|
|
3396
3522
|
$("#ai-question-custom-answer").value = "";
|
|
3523
|
+
$("#ai-question-navigation").classList.add("hidden");
|
|
3524
|
+
$("#ai-question-progress").textContent = "正在加载问题";
|
|
3397
3525
|
syncAiQuestionAnswerCount();
|
|
3398
3526
|
$("#ai-question-expiry").textContent = "正在加载问题……";
|
|
3399
3527
|
try {
|
|
@@ -3404,51 +3532,19 @@ async function openAiUserQuestionDialog(questionId) {
|
|
|
3404
3532
|
}
|
|
3405
3533
|
}
|
|
3406
3534
|
|
|
3407
|
-
function beginAiQuestionContinuationUi(conversationId
|
|
3535
|
+
function beginAiQuestionContinuationUi(conversationId) {
|
|
3408
3536
|
const tab = conversationId ? aiChatTabManager.findByConversation(conversationId) : null;
|
|
3409
3537
|
if (!tab) return null;
|
|
3410
|
-
const card = questionId
|
|
3411
|
-
? tab.feed.querySelector(`.ai-question-card[data-question-id="${CSS.escape(String(questionId))}"]`)
|
|
3412
|
-
: null;
|
|
3413
|
-
const note = card?.querySelector(".ai-interactive-note") ?? null;
|
|
3414
|
-
const status = card?.querySelector(".ai-status-chip") ?? null;
|
|
3415
|
-
const previousNote = note?.textContent ?? "";
|
|
3416
|
-
const previousStatus = status?.textContent ?? "";
|
|
3417
|
-
const controlStates = card
|
|
3418
|
-
? [...card.querySelectorAll("button")].map((button) => ({ button, disabled: button.disabled }))
|
|
3419
|
-
: [];
|
|
3420
|
-
if (card) {
|
|
3421
|
-
card.classList.add("is-resuming");
|
|
3422
|
-
card.setAttribute("aria-busy", "true");
|
|
3423
|
-
controlStates.forEach(({ button }) => { button.disabled = true; });
|
|
3424
|
-
if (status) status.textContent = "处理中";
|
|
3425
|
-
if (note) note.textContent = "回答已作为工具结果提交,正在根据你的回答继续处理…";
|
|
3426
|
-
}
|
|
3427
3538
|
aiQuestionContinuationTabIds.add(tab.id);
|
|
3428
3539
|
setAiChatTabStatus(tab, "streaming");
|
|
3429
3540
|
if (isActiveAiChatTab(tab)) syncAiRequestControls();
|
|
3430
3541
|
scrollAiFeedToBottom(tab.feed);
|
|
3431
|
-
return {
|
|
3432
|
-
tab,
|
|
3433
|
-
card,
|
|
3434
|
-
note,
|
|
3435
|
-
status,
|
|
3436
|
-
previousNote,
|
|
3437
|
-
previousStatus,
|
|
3438
|
-
controlStates
|
|
3439
|
-
};
|
|
3542
|
+
return { tab };
|
|
3440
3543
|
}
|
|
3441
3544
|
|
|
3442
3545
|
function finishAiQuestionContinuationUi(continuationUi, failed = false) {
|
|
3443
3546
|
if (!continuationUi) return;
|
|
3444
3547
|
aiQuestionContinuationTabIds.delete(continuationUi.tab.id);
|
|
3445
|
-
if (continuationUi.card?.isConnected) {
|
|
3446
|
-
continuationUi.card.classList.remove("is-resuming");
|
|
3447
|
-
continuationUi.card.removeAttribute("aria-busy");
|
|
3448
|
-
continuationUi.controlStates.forEach(({ button, disabled }) => { button.disabled = disabled; });
|
|
3449
|
-
if (continuationUi.note) continuationUi.note.textContent = continuationUi.previousNote;
|
|
3450
|
-
if (continuationUi.status) continuationUi.status.textContent = continuationUi.previousStatus;
|
|
3451
|
-
}
|
|
3452
3548
|
if (aiChatTabManager.get(continuationUi.tab.id)) setAiChatTabStatus(continuationUi.tab, failed ? "error" : "ready");
|
|
3453
3549
|
if (isActiveAiChatTab(continuationUi.tab)) syncAiRequestControls();
|
|
3454
3550
|
}
|
|
@@ -3481,26 +3577,21 @@ async function respondAiUserQuestion(questionId, payload) {
|
|
|
3481
3577
|
const conversationId = typeof knownQuestion?.conversationId === "string" ? knownQuestion.conversationId : null;
|
|
3482
3578
|
if (questionDialog.open) questionDialog.close();
|
|
3483
3579
|
if (approvalCenterDialog.open) approvalCenterDialog.close();
|
|
3484
|
-
continuationUi = beginAiQuestionContinuationUi(conversationId
|
|
3580
|
+
continuationUi = beginAiQuestionContinuationUi(conversationId);
|
|
3485
3581
|
let question;
|
|
3486
3582
|
if (payload.action === "reject") {
|
|
3487
3583
|
question = await api(questionsEndpoint(`/${encodeURIComponent(String(questionId))}/reject`), { method: "POST" });
|
|
3488
|
-
} else if (payload.action === "custom") {
|
|
3489
|
-
question = await api(questionsEndpoint(`/${encodeURIComponent(String(questionId))}/answer`), { method: "POST", body: { customAnswer: payload.customAnswer } });
|
|
3490
3584
|
} else {
|
|
3491
3585
|
question = await api(questionsEndpoint(`/${encodeURIComponent(String(questionId))}/answer`), {
|
|
3492
3586
|
method: "POST",
|
|
3493
|
-
body: {
|
|
3494
|
-
selectedOption: payload.selectedOption,
|
|
3495
|
-
...(payload.customAnswer ? { customAnswer: payload.customAnswer } : {})
|
|
3496
|
-
}
|
|
3587
|
+
body: { answers: payload.answers }
|
|
3497
3588
|
});
|
|
3498
3589
|
}
|
|
3499
3590
|
cacheAiQuestionView(question);
|
|
3500
3591
|
currentAiQuestionDialogView = question;
|
|
3501
3592
|
aiQuestionDialogQuestionId = String(question.id);
|
|
3502
3593
|
await reloadAiQuestionConversation(question.conversationId ?? conversationId);
|
|
3503
|
-
toast(payload.action === "reject" ? "
|
|
3594
|
+
toast(payload.action === "reject" ? "已跳过该提问批次,AI 已继续处理" : "全部回答已提交,AI 已继续处理");
|
|
3504
3595
|
return question;
|
|
3505
3596
|
} catch (error) {
|
|
3506
3597
|
continuationFailed = true;
|
|
@@ -3736,6 +3827,30 @@ async function favoriteAiConversation(conversation) {
|
|
|
3736
3827
|
toast(updated.isFavorite ? "对话已收藏" : "已取消收藏");
|
|
3737
3828
|
}
|
|
3738
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
|
+
|
|
3739
3854
|
async function deleteAiConversation(conversation) {
|
|
3740
3855
|
if (conversation.isFavorite === true) {
|
|
3741
3856
|
toast("收藏的对话不能清理,请先取消收藏", "error");
|
|
@@ -5390,8 +5505,11 @@ function showLineCitationMenu(event, lineIndex) {
|
|
|
5390
5505
|
selectChapterLines(lineIndex, lineIndex);
|
|
5391
5506
|
}
|
|
5392
5507
|
const menu = $("#line-citation-menu");
|
|
5393
|
-
|
|
5394
|
-
|
|
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));
|
|
5395
5513
|
const { start, end } = chapterLineSelection;
|
|
5396
5514
|
$("#line-citation-label").textContent = start === end ? `第 ${start + 1} 行` : `第 ${start + 1}-${end + 1} 行`;
|
|
5397
5515
|
menu.classList.remove("hidden");
|
|
@@ -6768,6 +6886,7 @@ async function loadWorks(preferredId) {
|
|
|
6768
6886
|
}
|
|
6769
6887
|
|
|
6770
6888
|
function restoredSettingsReturnContext(route) {
|
|
6889
|
+
if (route.returnView === "im") return { view: "im" };
|
|
6771
6890
|
if (route.returnView === "module" && route.returnModule) return { view: "module", module: route.returnModule };
|
|
6772
6891
|
if (route.returnView === "editor" && route.returnChapterId) return { view: "editor", chapterId: route.returnChapterId };
|
|
6773
6892
|
if (route.returnView === "welcome") return { view: "welcome" };
|
|
@@ -6812,6 +6931,10 @@ async function initializePage() {
|
|
|
6812
6931
|
showShelf();
|
|
6813
6932
|
return;
|
|
6814
6933
|
}
|
|
6934
|
+
if (route.view === "im") {
|
|
6935
|
+
await imWorkspace.open();
|
|
6936
|
+
return;
|
|
6937
|
+
}
|
|
6815
6938
|
|
|
6816
6939
|
const requestedWork = route.workId ? state.works.find((work) => work.id === route.workId) : null;
|
|
6817
6940
|
if (route.workId && !requestedWork) {
|
|
@@ -6893,6 +7016,7 @@ async function initializePage() {
|
|
|
6893
7016
|
}
|
|
6894
7017
|
|
|
6895
7018
|
function showShelf() {
|
|
7019
|
+
imWorkspace.close();
|
|
6896
7020
|
stopBackgroundTaskCenter();
|
|
6897
7021
|
dismissChapterInsightToast();
|
|
6898
7022
|
dismissDeleteToasts();
|
|
@@ -6917,6 +7041,7 @@ function showShelf() {
|
|
|
6917
7041
|
}
|
|
6918
7042
|
|
|
6919
7043
|
function captureSettingsReturnContext() {
|
|
7044
|
+
if (imWorkspace.opened) return { view: "im" };
|
|
6920
7045
|
if (!$("#shelf-view").classList.contains("hidden")) return { view: "shelf" };
|
|
6921
7046
|
if (!$("#editor-view").classList.contains("hidden")) return { view: "editor", chapterId: state.chapter?.id ?? null };
|
|
6922
7047
|
if (!$("#module-view").classList.contains("hidden")) return { view: "module", module: state.module };
|
|
@@ -6944,7 +7069,9 @@ function renderSettingsHub() {
|
|
|
6944
7069
|
$("#top-search-button").disabled = !canReadAggregate;
|
|
6945
7070
|
$("#export-button").disabled = !canExportManuscript;
|
|
6946
7071
|
$("#export-button").setAttribute("aria-expanded", "false");
|
|
6947
|
-
$("#settings-return").textContent = settingsReturnContext?.view === "
|
|
7072
|
+
$("#settings-return").textContent = settingsReturnContext?.view === "im"
|
|
7073
|
+
? "返回 IM"
|
|
7074
|
+
: settingsReturnContext?.view === "shelf" || !hasWork ? "返回书架" : "返回当前作品";
|
|
6948
7075
|
$("#settings-work-note").textContent = hasWork
|
|
6949
7076
|
? `当前作品:《${state.work.title}》。导出正文时可选择 Markdown ZIP 或 DOCX;DOCX 在有封面时会嵌入为首页。`
|
|
6950
7077
|
: "当前未选择作品;打开作品后可使用导出。";
|
|
@@ -8226,6 +8353,7 @@ async function showSettingsHub() {
|
|
|
8226
8353
|
settingsReturnContext = captureSettingsReturnContext();
|
|
8227
8354
|
state.dirty = false;
|
|
8228
8355
|
}
|
|
8356
|
+
imWorkspace.close();
|
|
8229
8357
|
dismissChapterInsightToast();
|
|
8230
8358
|
dismissTokenUsageDetails({ restoreFocus: false });
|
|
8231
8359
|
dismissDeleteToasts();
|
|
@@ -8262,6 +8390,7 @@ async function returnFromSettings() {
|
|
|
8262
8390
|
$("#platform-ai-view").classList.add("hidden");
|
|
8263
8391
|
$("#platform-usage-view").classList.add("hidden");
|
|
8264
8392
|
$("#work-audit-view").classList.add("hidden");
|
|
8393
|
+
if (context.view === "im") return imWorkspace.open();
|
|
8265
8394
|
if (context.view === "shelf" || !state.work) return showShelf();
|
|
8266
8395
|
$("#app").classList.remove("shelf-mode");
|
|
8267
8396
|
$("#shelf-view").classList.add("hidden");
|
|
@@ -8411,6 +8540,7 @@ function resetWorkScopedUiCaches() {
|
|
|
8411
8540
|
}
|
|
8412
8541
|
|
|
8413
8542
|
async function selectWork(workId, preferredChapterId = null) {
|
|
8543
|
+
imWorkspace.close();
|
|
8414
8544
|
chapterSelectionRequestGeneration += 1;
|
|
8415
8545
|
const discarding = state.work?.id !== workId && state.dirty;
|
|
8416
8546
|
if (discarding && !(await confirmDiscardChanges())) return false;
|
|
@@ -8534,7 +8664,7 @@ function renderTree() {
|
|
|
8534
8664
|
if (!state.work) return;
|
|
8535
8665
|
const count = state.work.volumes.reduce((total, volume) => total + Number(volume.chapterCount ?? volume.chapters?.length ?? 0), 0);
|
|
8536
8666
|
const proseEditable = canEditProse();
|
|
8537
|
-
$("#chapter-count").textContent =
|
|
8667
|
+
$("#chapter-count").querySelector(".chapter-count-number").textContent = String(count);
|
|
8538
8668
|
$("#reader-open-button").disabled = !canReadModule("editor") || count === 0;
|
|
8539
8669
|
$("#novel-tree").classList.remove("empty-copy");
|
|
8540
8670
|
$("#novel-tree").innerHTML = state.work.volumes.map((volume) => {
|
|
@@ -8555,7 +8685,7 @@ function renderTree() {
|
|
|
8555
8685
|
return `
|
|
8556
8686
|
<div class="volume-node ${collapsed ? "is-collapsed" : ""}" data-volume-id="${esc(volume.id)}">
|
|
8557
8687
|
<div class="volume-title">
|
|
8558
|
-
<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>
|
|
8559
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>` : ""}
|
|
8560
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>` : ""}
|
|
8561
8691
|
</div>
|
|
@@ -9104,6 +9234,7 @@ async function resolveCurrentChapterForeshadowReminder() {
|
|
|
9104
9234
|
}
|
|
9105
9235
|
|
|
9106
9236
|
async function selectChapter(chapterId, { editMode = false } = {}) {
|
|
9237
|
+
imWorkspace.close();
|
|
9107
9238
|
const workId = state.work?.id;
|
|
9108
9239
|
if (!workId) return false;
|
|
9109
9240
|
const selectionGeneration = ++chapterSelectionRequestGeneration;
|
|
@@ -9664,6 +9795,7 @@ function tidyChapterBlankLines() {
|
|
|
9664
9795
|
}
|
|
9665
9796
|
|
|
9666
9797
|
function showWelcome(hasWork = false) {
|
|
9798
|
+
imWorkspace.close();
|
|
9667
9799
|
chapterSelectionRequestId += 1;
|
|
9668
9800
|
clearChapterForeshadowReminders({ invalidateRequest: true });
|
|
9669
9801
|
dismissChapterInsightToast();
|
|
@@ -9697,6 +9829,7 @@ const moduleMeta = {
|
|
|
9697
9829
|
};
|
|
9698
9830
|
|
|
9699
9831
|
async function showModule(module) {
|
|
9832
|
+
imWorkspace.close();
|
|
9700
9833
|
if (!state.work) return showWelcome();
|
|
9701
9834
|
if (!canReadModule(module)) {
|
|
9702
9835
|
const fallback = firstReadableUiModule(state.work);
|
|
@@ -18509,8 +18642,21 @@ function appendMessage(role, text, citations = [], createdAt = null, metadata =
|
|
|
18509
18642
|
const references = document.createElement("div");
|
|
18510
18643
|
references.className = "message-citations";
|
|
18511
18644
|
for (const citation of citations) {
|
|
18512
|
-
const reference = document.createElement("
|
|
18513
|
-
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
|
+
});
|
|
18514
18660
|
references.append(reference);
|
|
18515
18661
|
}
|
|
18516
18662
|
message.append(references);
|
|
@@ -20729,6 +20875,7 @@ document.addEventListener("pointerdown", (event) => {
|
|
|
20729
20875
|
if (!event.target.closest(".prompt-composer")) hideAiMentionMenu();
|
|
20730
20876
|
if (!event.target.closest("#ai-context-meter") && !event.target.closest("#ai-context-popover")) setAiContextDistributionVisible(false);
|
|
20731
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();
|
|
20732
20879
|
if (!event.target.closest("#account-button") && !event.target.closest("#account-menu")) {
|
|
20733
20880
|
$("#account-menu").classList.add("hidden");
|
|
20734
20881
|
$("#account-button").setAttribute("aria-expanded", "false");
|
|
@@ -20755,6 +20902,10 @@ document.addEventListener("keydown", (event) => {
|
|
|
20755
20902
|
setAiModelPickerVisible(false);
|
|
20756
20903
|
return;
|
|
20757
20904
|
}
|
|
20905
|
+
if (!$("#ai-citation-popover").classList.contains("hidden")) {
|
|
20906
|
+
closeAiCitationPopover({ restoreFocus: true });
|
|
20907
|
+
return;
|
|
20908
|
+
}
|
|
20758
20909
|
if (!$("#chapter-search-panel").classList.contains("hidden")) {
|
|
20759
20910
|
closeChapterSearchPanel();
|
|
20760
20911
|
return;
|
|
@@ -20801,6 +20952,7 @@ $("#ai-context-meter").addEventListener("click", () => {
|
|
|
20801
20952
|
setAiContextDistributionVisible($("#ai-context-popover").classList.contains("hidden"));
|
|
20802
20953
|
});
|
|
20803
20954
|
$("#ai-context-popover-close").addEventListener("click", () => setAiContextDistributionVisible(false));
|
|
20955
|
+
$("#ai-citation-popover-close").addEventListener("click", () => closeAiCitationPopover({ restoreFocus: true }));
|
|
20804
20956
|
$("#ai-send").addEventListener("click", activateAiSendControl);
|
|
20805
20957
|
$("#ai-conversation-switcher").addEventListener("click", () => {
|
|
20806
20958
|
setAiConversationSwitcherVisible($("#ai-conversation-switcher-menu").classList.contains("hidden"));
|
|
@@ -20945,6 +21097,19 @@ $("#ai-history-action-menu").addEventListener("click", async (event) => {
|
|
|
20945
21097
|
}
|
|
20946
21098
|
return;
|
|
20947
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
|
+
}
|
|
20948
21113
|
if (action === "copy-session-id") {
|
|
20949
21114
|
option.disabled = true;
|
|
20950
21115
|
try {
|
|
@@ -21042,14 +21207,17 @@ $("#ai-write-plan-undo").addEventListener("click", () => {
|
|
|
21042
21207
|
$("#ai-question-close").addEventListener("click", () => $("#ai-question-dialog").close());
|
|
21043
21208
|
|
|
21044
21209
|
function syncAiQuestionSubmitState() {
|
|
21045
|
-
const checked = document.querySelector('input[name="ai-question-choice"]:checked');
|
|
21046
|
-
const customInput = $("#ai-question-custom-answer");
|
|
21047
21210
|
const submit = $("#ai-question-submit");
|
|
21048
|
-
const
|
|
21049
|
-
|
|
21050
|
-
|
|
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;
|
|
21051
21219
|
submit.disabled = disabled;
|
|
21052
|
-
submit.title =
|
|
21220
|
+
submit.title = unansweredCount > 0 ? `还有 ${unansweredCount} 个问题未回答` : "";
|
|
21053
21221
|
}
|
|
21054
21222
|
$("#ai-question-form").addEventListener("change", (event) => {
|
|
21055
21223
|
const control = event.target;
|
|
@@ -21057,6 +21225,7 @@ $("#ai-question-form").addEventListener("change", (event) => {
|
|
|
21057
21225
|
const isCustom = control.value === "custom";
|
|
21058
21226
|
const customInput = $("#ai-question-custom-answer");
|
|
21059
21227
|
if (isCustom && !control.disabled) customInput.focus();
|
|
21228
|
+
updateCurrentAiQuestionDraft();
|
|
21060
21229
|
syncAiQuestionOptionPresentation();
|
|
21061
21230
|
syncAiQuestionSubmitState();
|
|
21062
21231
|
});
|
|
@@ -21068,23 +21237,30 @@ $("#ai-question-custom-answer").addEventListener("input", () => {
|
|
|
21068
21237
|
const customRadio = document.querySelector('input[name="ai-question-choice"][value="custom"]');
|
|
21069
21238
|
if (customRadio && !customRadio.disabled) customRadio.checked = true;
|
|
21070
21239
|
}
|
|
21240
|
+
updateCurrentAiQuestionDraft();
|
|
21071
21241
|
syncAiQuestionOptionPresentation();
|
|
21072
21242
|
syncAiQuestionSubmitState();
|
|
21073
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));
|
|
21074
21254
|
$("#ai-question-submit").addEventListener("click", () => {
|
|
21075
|
-
|
|
21076
|
-
|
|
21077
|
-
if (
|
|
21078
|
-
const text = String($("#ai-question-custom-answer").value).trim();
|
|
21079
|
-
if (!text) return;
|
|
21080
|
-
respondAiUserQuestion(aiQuestionDialogQuestionId, { action: "custom", customAnswer: text }).catch((error) => toast(error.message, "error"));
|
|
21081
|
-
return;
|
|
21082
|
-
}
|
|
21083
|
-
const supplementalAnswer = String($("#ai-question-custom-answer").value).trim();
|
|
21255
|
+
if (aiQuestionDialogQuestionId == null) return;
|
|
21256
|
+
updateCurrentAiQuestionDraft();
|
|
21257
|
+
if (!aiQuestionDrafts.every(aiQuestionDraftIsAnswered)) return syncAiQuestionSubmitState();
|
|
21084
21258
|
respondAiUserQuestion(aiQuestionDialogQuestionId, {
|
|
21085
|
-
action: "
|
|
21086
|
-
|
|
21087
|
-
|
|
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
|
+
}))
|
|
21088
21264
|
}).catch((error) => toast(error.message, "error"));
|
|
21089
21265
|
});
|
|
21090
21266
|
$("#ai-question-skip").addEventListener("click", () => {
|
|
@@ -21233,6 +21409,7 @@ window.addEventListener("online", () => {
|
|
|
21233
21409
|
});
|
|
21234
21410
|
window.addEventListener("offline", () => updateSystemHealth({ status: "offline" }));
|
|
21235
21411
|
window.addEventListener("resize", () => {
|
|
21412
|
+
if (aiCitationPopoverTrigger && !$("#ai-citation-popover").classList.contains("hidden")) positionAiCitationPopover(aiCitationPopoverTrigger);
|
|
21236
21413
|
if ($("#reader-view").classList.contains("hidden") || readingPreferences.mode !== "paged") return;
|
|
21237
21414
|
if (readingResizeFrame !== null) window.cancelAnimationFrame(readingResizeFrame);
|
|
21238
21415
|
readingResizeFrame = window.requestAnimationFrame(() => {
|
|
@@ -21241,7 +21418,25 @@ window.addEventListener("resize", () => {
|
|
|
21241
21418
|
});
|
|
21242
21419
|
});
|
|
21243
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
|
+
|
|
21244
21438
|
initializeAiChatTabs();
|
|
21439
|
+
imWorkspace.start();
|
|
21245
21440
|
initializePage().catch((error) => {
|
|
21246
21441
|
restoringPageRoute = false;
|
|
21247
21442
|
if (state.user) {
|
|
@@ -21250,4 +21445,6 @@ initializePage().catch((error) => {
|
|
|
21250
21445
|
}
|
|
21251
21446
|
showShelf();
|
|
21252
21447
|
toast(`系统初始化失败:${error.message}`, "error");
|
|
21448
|
+
}).finally(() => {
|
|
21449
|
+
if (state.user) void imWorkspace.activate().catch((error) => toast(`IM 初始化失败:${error.message}`, "error"));
|
|
21253
21450
|
});
|