@musnows/scriverse 0.5.9 → 0.5.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.js +5 -3
- package/dist/ai.js.map +1 -1
- package/dist/app.js +2 -2
- package/dist/app.js.map +1 -1
- package/dist/public/app.js +215 -19
- package/dist/public/index.html +2 -2
- package/dist/public/styles.css +13 -6
- package/dist/public/vditor-line-number-layout.d.ts +14 -0
- package/dist/public/vditor-line-number-layout.js +51 -0
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
package/dist/public/app.js
CHANGED
|
@@ -4,6 +4,7 @@ import { renderMarkdown } from "/markdown.js?v=20260725-ordered-list";
|
|
|
4
4
|
import { buildAiReferenceScope, findAiMention, listAiMentionOptions } from "/ai-mentions.js?v=20260716-chapter-references";
|
|
5
5
|
import { shouldShowAiQuickActions } from "/ai-conversation.js?v=20260713-quick-actions";
|
|
6
6
|
import { calculateLineNumberRowHeight, calculateLineNumberRowTop, calculateLineNumberTextOffset, calculateLineNumberTop } from "/line-number-layout.js?v=20260713-row-box-alignment";
|
|
7
|
+
import { buildVditorLineNumberRows } from "/vditor-line-number-layout.js?v=20260729-vditor-line-numbers-v3";
|
|
7
8
|
import { MODEL_PURPOSE_OPTIONS, isKimiModelId, modelFormValues, modelOptionLabel, modelPayload } from "/model-config.js?v=20260723-kimi-temperature";
|
|
8
9
|
import { shouldSendAiPrompt } from "/ai-prompt-keyboard.js?v=20260713-enter-to-send";
|
|
9
10
|
import { estimateAiMessageTokens, formatAiMessageMeta } from "/ai-message-meta.js?v=20260726-cache-hit-percent";
|
|
@@ -141,6 +142,8 @@ let chapterMovePending = false;
|
|
|
141
142
|
|
|
142
143
|
let timelineMultiSelectEnabled = false;
|
|
143
144
|
let taskProgressRefreshTimer = null;
|
|
145
|
+
let taskAutoRunEditing = false;
|
|
146
|
+
let taskAutoRunEditingWorkId = null;
|
|
144
147
|
let relationshipSearchIndexRefreshTimer = null;
|
|
145
148
|
let backgroundTaskCenterTimer = null;
|
|
146
149
|
let backgroundTaskCenterRequest = 0;
|
|
@@ -3749,6 +3752,10 @@ async function showModule(module) {
|
|
|
3749
3752
|
}
|
|
3750
3753
|
module = fallback;
|
|
3751
3754
|
}
|
|
3755
|
+
if (module !== "tasks") {
|
|
3756
|
+
taskAutoRunEditing = false;
|
|
3757
|
+
taskAutoRunEditingWorkId = null;
|
|
3758
|
+
}
|
|
3752
3759
|
if (module !== "editor" && state.module === "editor" && !(await confirmDiscardChanges())) return;
|
|
3753
3760
|
if (module !== "editor" && state.module === "editor" && state.dirty) setSaveState("已放弃修改");
|
|
3754
3761
|
state.module = module;
|
|
@@ -4771,12 +4778,14 @@ async function renderTasks(page = taskListPage) {
|
|
|
4771
4778
|
const taskTotal = Number(taskPage.total ?? taskPage.stats?.total ?? tasks.length);
|
|
4772
4779
|
mountModuleCount(taskTotal);
|
|
4773
4780
|
const canConfigureAutoRun = canEditModule("tasks") && canEditModule("ai-settings");
|
|
4781
|
+
const autoRunEditing = canConfigureAutoRun && taskAutoRunEditing && taskAutoRunEditingWorkId === state.work.id;
|
|
4774
4782
|
const pendingCount = Number(taskPage.stats?.pendingCount ?? 0);
|
|
4775
4783
|
const runningCount = Number(taskPage.stats?.runningCount ?? 0);
|
|
4776
4784
|
const activeTaskCount = pendingCount + runningCount;
|
|
4777
4785
|
const runningProgress = runningCount ? analysisTaskProgressValue(taskPage.stats?.runningProgress) : 0;
|
|
4778
4786
|
const autoRunPaused = Boolean(settings.autoRunEnabled && settings.autoRunPaused);
|
|
4779
4787
|
const autoRunActive = Boolean(settings.autoRunEnabled && !autoRunPaused);
|
|
4788
|
+
const autoRunPauseLabel = autoRunPaused ? "恢复自动执行" : "暂停自动执行";
|
|
4780
4789
|
const queueProgressLabel = runningCount
|
|
4781
4790
|
? "运行中任务平均进度"
|
|
4782
4791
|
: autoRunPaused ? "自动执行已暂停" : autoRunActive ? "等待任务开始" : "自动执行已关闭";
|
|
@@ -4799,13 +4808,17 @@ async function renderTasks(page = taskListPage) {
|
|
|
4799
4808
|
<small>只执行已经进入“待执行”队列的任务,不会自动创建人物关系、世界观或其他分析。</small>
|
|
4800
4809
|
<small>开启后会持续执行直到队列清空;临时错误自动退避重试,连续失败达到阈值后暂停。</small>
|
|
4801
4810
|
</div>
|
|
4811
|
+
<div class="task-auto-run-actions">
|
|
4812
|
+
${autoRunEditing ? "" : '<button id="task-auto-run-edit" class="ghost-button" type="button">编辑</button>'}
|
|
4813
|
+
</div>
|
|
4802
4814
|
<div class="task-auto-run-controls">
|
|
4803
|
-
<label class="checkbox-field"><input id="task-auto-run-enabled" type="checkbox" ${settings.autoRunEnabled ? "checked" : ""}><span>持续自动执行</span></label>
|
|
4804
|
-
<label>同时运行上限<input id="task-auto-run-concurrency" type="number" min="1" max="8" value="${esc(String(settings.autoRunConcurrency ?? 2))}"></label>
|
|
4805
|
-
<label>每日任务上限<input id="task-auto-run-daily-limit" type="number" min="0" max="10000" value="${esc(String(settings.autoRunDailyTaskLimit ?? 0))}" aria-describedby="task-auto-run-daily-help"></label>
|
|
4806
|
-
<label>连续失败暂停阈值<input id="task-auto-run-failure-threshold" type="number" min="1" max="10" value="${esc(String(settings.autoRunFailureThreshold ?? 3))}"></label>
|
|
4807
|
-
|
|
4808
|
-
|
|
4815
|
+
<label class="checkbox-field"><input id="task-auto-run-enabled" type="checkbox" ${settings.autoRunEnabled ? "checked" : ""} ${autoRunEditing ? "" : "disabled"}><span>持续自动执行</span></label>
|
|
4816
|
+
<label>同时运行上限<input id="task-auto-run-concurrency" type="number" min="1" max="8" value="${esc(String(settings.autoRunConcurrency ?? 2))}" ${autoRunEditing ? "" : "disabled"} aria-readonly="${String(!autoRunEditing)}"></label>
|
|
4817
|
+
<label>每日任务上限<input id="task-auto-run-daily-limit" type="number" min="0" max="10000" value="${esc(String(settings.autoRunDailyTaskLimit ?? 0))}" ${autoRunEditing ? "" : "disabled"} aria-readonly="${String(!autoRunEditing)}" aria-describedby="task-auto-run-daily-help"></label>
|
|
4818
|
+
<label>连续失败暂停阈值<input id="task-auto-run-failure-threshold" type="number" min="1" max="10" value="${esc(String(settings.autoRunFailureThreshold ?? 3))}" ${autoRunEditing ? "" : "disabled"} aria-readonly="${String(!autoRunEditing)}"></label>
|
|
4819
|
+
${autoRunEditing
|
|
4820
|
+
? `<button id="task-auto-run-save" class="primary-button" type="button">保存并生效</button><button id="task-auto-run-pause" class="ghost-button" type="button">${autoRunPauseLabel}</button>`
|
|
4821
|
+
: ""}
|
|
4809
4822
|
</div>
|
|
4810
4823
|
<p id="task-auto-run-daily-help" class="task-auto-run-help">每日任务上限填 0 表示不限制;达到上限后会在下一个 UTC 自然日自动恢复。</p>
|
|
4811
4824
|
<p class="task-auto-run-meta">待执行队列 ${pendingCount} 个 · 正在运行 ${runningCount} 个 · ${autoRunPaused ? "已暂停" : autoRunActive ? "持续执行中" : "未开启"}</p>
|
|
@@ -4844,6 +4857,12 @@ async function renderTasks(page = taskListPage) {
|
|
|
4844
4857
|
await renderTasks(Number(button.dataset.taskPage));
|
|
4845
4858
|
}));
|
|
4846
4859
|
|
|
4860
|
+
$("#task-auto-run-edit")?.addEventListener("click", () => {
|
|
4861
|
+
taskAutoRunEditing = true;
|
|
4862
|
+
taskAutoRunEditingWorkId = state.work.id;
|
|
4863
|
+
void renderTasks(taskListPage).catch((error) => toast(error.message, "error"));
|
|
4864
|
+
});
|
|
4865
|
+
|
|
4847
4866
|
$("#task-auto-run-save")?.addEventListener("click", async () => {
|
|
4848
4867
|
const button = $("#task-auto-run-save");
|
|
4849
4868
|
button.disabled = true;
|
|
@@ -4860,15 +4879,16 @@ async function renderTasks(page = taskListPage) {
|
|
|
4860
4879
|
toast(updated.autoRunEnabled
|
|
4861
4880
|
? `持续自动执行已开启:同时最多 ${updated.autoRunConcurrency} 个`
|
|
4862
4881
|
: "自动执行已关闭");
|
|
4882
|
+
taskAutoRunEditing = false;
|
|
4883
|
+
taskAutoRunEditingWorkId = null;
|
|
4863
4884
|
await renderTasks();
|
|
4864
|
-
window.setTimeout(() => $("#task-auto-run-save")?.focus(), 0);
|
|
4865
4885
|
} catch (error) {
|
|
4866
4886
|
toast(error.message, "error");
|
|
4867
4887
|
button.disabled = false;
|
|
4868
4888
|
}
|
|
4869
4889
|
});
|
|
4870
|
-
$("#task-auto-run-
|
|
4871
|
-
const button = $("#task-auto-run-
|
|
4890
|
+
$("#task-auto-run-pause")?.addEventListener("click", async () => {
|
|
4891
|
+
const button = $("#task-auto-run-pause");
|
|
4872
4892
|
button.disabled = true;
|
|
4873
4893
|
try {
|
|
4874
4894
|
if (autoRunPaused) {
|
|
@@ -4876,17 +4896,18 @@ async function renderTasks(page = taskListPage) {
|
|
|
4876
4896
|
toast("自动执行已恢复");
|
|
4877
4897
|
} else {
|
|
4878
4898
|
await api(`/api/works/${state.work.id}/ai-settings`, { method: "PATCH", body: { autoRunEnabled: false } });
|
|
4879
|
-
toast("
|
|
4899
|
+
toast("已暂停自动执行");
|
|
4880
4900
|
}
|
|
4901
|
+
taskAutoRunEditing = false;
|
|
4902
|
+
taskAutoRunEditingWorkId = null;
|
|
4881
4903
|
await refreshBackgroundTaskCenter({ announce: false });
|
|
4882
4904
|
await renderTasks();
|
|
4883
|
-
window.setTimeout(() => $(
|
|
4905
|
+
window.setTimeout(() => $("#task-auto-run-edit")?.focus(), 0);
|
|
4884
4906
|
} catch (error) {
|
|
4885
4907
|
toast(error.message, "error");
|
|
4886
4908
|
button.disabled = false;
|
|
4887
4909
|
}
|
|
4888
4910
|
});
|
|
4889
|
-
|
|
4890
4911
|
$("#module-content").querySelectorAll("[data-task-detail]").forEach((button) => button.addEventListener("click", () => {
|
|
4891
4912
|
if (button.disabled) return;
|
|
4892
4913
|
button.disabled = true;
|
|
@@ -4957,6 +4978,51 @@ async function rerunAnalysisTask(taskId, button, { closeDetail = false } = {}) {
|
|
|
4957
4978
|
}
|
|
4958
4979
|
}
|
|
4959
4980
|
|
|
4981
|
+
async function rerunAnalysisTaskWithModel(task, button) {
|
|
4982
|
+
const originalLabel = button.textContent;
|
|
4983
|
+
button.disabled = true;
|
|
4984
|
+
button.textContent = "加载模型";
|
|
4985
|
+
try {
|
|
4986
|
+
const models = await api(`/api/works/${encodeURIComponent(task.workId)}/models`);
|
|
4987
|
+
const currentModelId = String(task.model?.id ?? "");
|
|
4988
|
+
const availableModels = models.filter((model) =>
|
|
4989
|
+
model.id !== currentModelId
|
|
4990
|
+
&& model.enabled
|
|
4991
|
+
&& model.providerStatus === "enabled"
|
|
4992
|
+
&& model.providerConnectionStatus === "success"
|
|
4993
|
+
);
|
|
4994
|
+
if (!availableModels.length) throw new Error("当前没有其他可用模型,请先配置并测试模型");
|
|
4995
|
+
$("#form-dialog").close();
|
|
4996
|
+
const currentModelLabel = task.model?.displayName || "运行时默认模型";
|
|
4997
|
+
openDialog("选择重试模型",
|
|
4998
|
+
`<p class="task-rerun-model-intro">当前模型:${esc(currentModelLabel)}。选择其他模型后,将按原任务范围和最新资料创建新任务。</p><label>重试模型<select name="modelId" required aria-label="重试模型">
|
|
4999
|
+
${availableModels.map((model) => `<option value="${esc(model.id)}">${esc(modelOptionLabel(model))}</option>`).join("")}
|
|
5000
|
+
</select></label>`,
|
|
5001
|
+
async (form) => {
|
|
5002
|
+
const modelId = String(form.get("modelId") ?? "").trim();
|
|
5003
|
+
const selectedModel = availableModels.find((model) => model.id === modelId);
|
|
5004
|
+
const rerun = await api(`/api/tasks/${encodeURIComponent(task.id)}/rerun`, {
|
|
5005
|
+
method: "POST",
|
|
5006
|
+
body: { modelId }
|
|
5007
|
+
});
|
|
5008
|
+
toast(`已使用${selectedModel ? `“${modelOptionLabel(selectedModel)}”` : "新模型"}创建重试任务 ${rerun.id}`);
|
|
5009
|
+
await refreshBackgroundTaskCenter({ announce: false });
|
|
5010
|
+
if (state.module === "tasks" && state.work?.id === task.workId) await renderTasks();
|
|
5011
|
+
},
|
|
5012
|
+
"AI 分析重试",
|
|
5013
|
+
{
|
|
5014
|
+
submitLabel: "换模型重试",
|
|
5015
|
+
pendingLabel: "创建中…",
|
|
5016
|
+
pendingMessage: "正在使用新模型创建重试任务,请稍候",
|
|
5017
|
+
errorPrefix: "换模型重试失败:"
|
|
5018
|
+
});
|
|
5019
|
+
} catch (error) {
|
|
5020
|
+
toast(error.message, "error");
|
|
5021
|
+
button.disabled = false;
|
|
5022
|
+
button.textContent = originalLabel;
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
|
|
4960
5026
|
function stopTaskProgressRefresh() {
|
|
4961
5027
|
if (taskProgressRefreshTimer === null) return;
|
|
4962
5028
|
window.clearTimeout(taskProgressRefreshTimer);
|
|
@@ -5407,7 +5473,7 @@ function openTaskDetailDialog(task, trace) {
|
|
|
5407
5473
|
<div><strong>范围详情</strong><ul>${detailHtml}</ul></div>
|
|
5408
5474
|
<div><strong>失败信息</strong>${failureHtml}${identityRepairHtml}</div>
|
|
5409
5475
|
<div><strong>结果摘要</strong>${resultPreview}</div>
|
|
5410
|
-
${canRerunAnalysisTask(task) ? `<div class="task-detail-actions"><button class="primary-button" type="button" data-rerun-task-detail="${esc(task.id)}">按原配置重新执行</button><small>新任务会重新读取当前正文、设定和人物资料,旧任务记录保持不变。</small></div>` : ""}
|
|
5476
|
+
${canRerunAnalysisTask(task) ? `<div class="task-detail-actions"><button class="primary-button" type="button" data-rerun-task-detail="${esc(task.id)}">按原配置重新执行</button><button class="ghost-button" type="button" data-rerun-task-model="${esc(task.id)}">换模型重试</button><small>新任务会重新读取当前正文、设定和人物资料,旧任务记录保持不变。</small></div>` : ""}
|
|
5411
5477
|
</section>
|
|
5412
5478
|
${renderTaskTraceVisualization(trace, task.id)}
|
|
5413
5479
|
</div>`,
|
|
@@ -5419,6 +5485,9 @@ function openTaskDetailDialog(task, trace) {
|
|
|
5419
5485
|
$("#dialog-fields").querySelector("[data-rerun-task-detail]")?.addEventListener("click", async (event) => {
|
|
5420
5486
|
await rerunAnalysisTask(event.currentTarget.dataset.rerunTaskDetail, event.currentTarget, { closeDetail: true });
|
|
5421
5487
|
});
|
|
5488
|
+
$("#dialog-fields").querySelector("[data-rerun-task-model]")?.addEventListener("click", async (event) => {
|
|
5489
|
+
await rerunAnalysisTaskWithModel(task, event.currentTarget);
|
|
5490
|
+
});
|
|
5422
5491
|
$("#dialog-fields").querySelector("[data-task-identity-repair]")?.addEventListener("click", async (event) => {
|
|
5423
5492
|
const button = event.currentTarget;
|
|
5424
5493
|
button.disabled = true;
|
|
@@ -6962,6 +7031,107 @@ function toggleVditorLineNumbers(editor) {
|
|
|
6962
7031
|
const enabled = host.classList.toggle("show-line-numbers");
|
|
6963
7032
|
const button = host.querySelector(".vditor-line-number-button");
|
|
6964
7033
|
if (button) button.setAttribute("aria-pressed", String(enabled));
|
|
7034
|
+
updateVditorLineNumbers(editor, getVditorMarkdown(editor));
|
|
7035
|
+
}
|
|
7036
|
+
|
|
7037
|
+
function getVditorMarkdown(editor) {
|
|
7038
|
+
if (typeof editor?.getValue === "function") {
|
|
7039
|
+
try {
|
|
7040
|
+
return String(editor.getValue() ?? "");
|
|
7041
|
+
} catch {
|
|
7042
|
+
return "";
|
|
7043
|
+
}
|
|
7044
|
+
}
|
|
7045
|
+
const host = editor?.vditor?.element;
|
|
7046
|
+
const valueField = host?.closest("[data-vditor-editor-field], .setting-markdown-field, .character-markdown-editor")?.querySelector("[data-vditor-value]")
|
|
7047
|
+
?? host?.parentElement?.querySelector("[data-vditor-value]");
|
|
7048
|
+
return String(valueField?.value ?? "");
|
|
7049
|
+
}
|
|
7050
|
+
|
|
7051
|
+
function getVditorVisibleSurface(content) {
|
|
7052
|
+
const candidates = [
|
|
7053
|
+
content.querySelector(".vditor-ir .vditor-reset"),
|
|
7054
|
+
content.querySelector(".vditor-wysiwyg .vditor-reset"),
|
|
7055
|
+
content.querySelector(".vditor-sv textarea"),
|
|
7056
|
+
content.querySelector(".vditor-sv")
|
|
7057
|
+
];
|
|
7058
|
+
return candidates.find((surface) => {
|
|
7059
|
+
if (!surface) return false;
|
|
7060
|
+
const rect = surface.getBoundingClientRect();
|
|
7061
|
+
const style = getComputedStyle(surface);
|
|
7062
|
+
return rect.width > 0 && rect.height > 0 && style.display !== "none" && style.visibility !== "hidden";
|
|
7063
|
+
}) ?? null;
|
|
7064
|
+
}
|
|
7065
|
+
|
|
7066
|
+
function getVditorLineHeight(surface) {
|
|
7067
|
+
const style = getComputedStyle(surface);
|
|
7068
|
+
return parseFloat(style.lineHeight) || parseFloat(style.fontSize) * 1.5 || 24;
|
|
7069
|
+
}
|
|
7070
|
+
|
|
7071
|
+
function collectVditorVisualLineRects(surface) {
|
|
7072
|
+
const rects = [];
|
|
7073
|
+
const scrollTop = Number(surface.scrollTop) || 0;
|
|
7074
|
+
const visit = (node) => {
|
|
7075
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
7076
|
+
if (!node.textContent) return;
|
|
7077
|
+
const range = document.createRange();
|
|
7078
|
+
range.selectNodeContents(node);
|
|
7079
|
+
[...range.getClientRects()].forEach((rect) => {
|
|
7080
|
+
if (rect.height > 0) rects.push({ top: rect.top + scrollTop, height: rect.height });
|
|
7081
|
+
});
|
|
7082
|
+
return;
|
|
7083
|
+
}
|
|
7084
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
|
7085
|
+
if (node.tagName === "BR") {
|
|
7086
|
+
const rect = node.getBoundingClientRect();
|
|
7087
|
+
if (rect.height > 0) rects.push({ top: rect.top + scrollTop, height: rect.height });
|
|
7088
|
+
}
|
|
7089
|
+
[...node.childNodes].forEach(visit);
|
|
7090
|
+
};
|
|
7091
|
+
visit(surface);
|
|
7092
|
+
if (rects.length === 0) {
|
|
7093
|
+
const rect = surface.getBoundingClientRect();
|
|
7094
|
+
const style = getComputedStyle(surface);
|
|
7095
|
+
rects.push({ top: rect.top + scrollTop + (parseFloat(style.paddingTop) || 0), height: getVditorLineHeight(surface) });
|
|
7096
|
+
}
|
|
7097
|
+
return rects;
|
|
7098
|
+
}
|
|
7099
|
+
|
|
7100
|
+
function syncVditorLineNumberScroll(editor) {
|
|
7101
|
+
const surface = editor?.__vditorLineNumberSurface;
|
|
7102
|
+
const gutter = editor?.vditor?.element?.querySelector(".vditor-line-numbers");
|
|
7103
|
+
if (!surface || !gutter) return;
|
|
7104
|
+
gutter.style.transform = `translateY(${-surface.scrollTop}px)`;
|
|
7105
|
+
gutter.dataset.scrollTop = String(surface.scrollTop);
|
|
7106
|
+
}
|
|
7107
|
+
|
|
7108
|
+
function scheduleVditorLineNumbers(editor) {
|
|
7109
|
+
if (!editor || editor.__vditorLineNumberFrame) return;
|
|
7110
|
+
editor.__vditorLineNumberFrame = requestAnimationFrame(() => {
|
|
7111
|
+
editor.__vditorLineNumberFrame = null;
|
|
7112
|
+
updateVditorLineNumbers(editor, getVditorMarkdown(editor));
|
|
7113
|
+
});
|
|
7114
|
+
}
|
|
7115
|
+
|
|
7116
|
+
function setupVditorLineNumberSync(editor, content, surface) {
|
|
7117
|
+
if (editor.__vditorLineNumberSurface !== surface) {
|
|
7118
|
+
editor.__vditorLineNumberSurface?.removeEventListener("scroll", editor.__vditorLineNumberScrollHandler);
|
|
7119
|
+
editor.__vditorLineNumberSurface = surface;
|
|
7120
|
+
editor.__vditorLineNumberScrollHandler = () => syncVditorLineNumberScroll(editor);
|
|
7121
|
+
surface.addEventListener("scroll", editor.__vditorLineNumberScrollHandler, { passive: true });
|
|
7122
|
+
}
|
|
7123
|
+
if (!editor.__vditorLineNumberObserver) {
|
|
7124
|
+
editor.__vditorLineNumberObserver = new MutationObserver((mutations) => {
|
|
7125
|
+
if (mutations.some((mutation) => !(mutation.target instanceof Element) || !mutation.target.closest(".vditor-line-numbers"))) {
|
|
7126
|
+
scheduleVditorLineNumbers(editor);
|
|
7127
|
+
}
|
|
7128
|
+
});
|
|
7129
|
+
editor.__vditorLineNumberObserver.observe(content, { subtree: true, childList: true, attributes: true, attributeFilter: ["class", "style"] });
|
|
7130
|
+
}
|
|
7131
|
+
if (!editor.__vditorLineNumberResizeObserver && typeof ResizeObserver !== "undefined") {
|
|
7132
|
+
editor.__vditorLineNumberResizeObserver = new ResizeObserver(() => scheduleVditorLineNumbers(editor));
|
|
7133
|
+
editor.__vditorLineNumberResizeObserver.observe(content);
|
|
7134
|
+
}
|
|
6965
7135
|
}
|
|
6966
7136
|
|
|
6967
7137
|
function updateVditorLineNumbers(editor, markdown) {
|
|
@@ -6974,14 +7144,30 @@ function updateVditorLineNumbers(editor, markdown) {
|
|
|
6974
7144
|
gutter.setAttribute("aria-hidden", "true");
|
|
6975
7145
|
content.prepend(gutter);
|
|
6976
7146
|
}
|
|
6977
|
-
const
|
|
7147
|
+
const surface = getVditorVisibleSurface(content);
|
|
7148
|
+
if (!surface) return;
|
|
7149
|
+
setupVditorLineNumberSync(editor, content, surface);
|
|
7150
|
+
const contentRect = content.getBoundingClientRect();
|
|
7151
|
+
const lineHeight = getVditorLineHeight(surface);
|
|
7152
|
+
const normalizedMarkdown = String(markdown ?? "").replace(/\r\n?/gu, "\n");
|
|
7153
|
+
const blankLineCount = normalizedMarkdown.trim() === "" ? 0 : normalizedMarkdown.split("\n").filter((line) => line.trim() === "").length;
|
|
7154
|
+
const visualRows = buildVditorLineNumberRows(collectVditorVisualLineRects(surface), { lineHeight, blankLineCount });
|
|
7155
|
+
gutter.style.height = `${content.clientHeight}px`;
|
|
6978
7156
|
const fragment = document.createDocumentFragment();
|
|
6979
|
-
|
|
7157
|
+
visualRows.forEach((row, index) => {
|
|
6980
7158
|
const number = document.createElement("span");
|
|
6981
|
-
number.
|
|
7159
|
+
number.className = "vditor-line-number";
|
|
7160
|
+
number.textContent = String(index + 1);
|
|
7161
|
+
number.dataset.lineNumber = String(index + 1);
|
|
7162
|
+
number.dataset.blank = String(row.blank);
|
|
7163
|
+
number.style.top = `${row.top - contentRect.top}px`;
|
|
7164
|
+
number.style.height = `${row.height}px`;
|
|
7165
|
+
number.style.lineHeight = `${row.height}px`;
|
|
6982
7166
|
fragment.append(number);
|
|
6983
|
-
}
|
|
7167
|
+
});
|
|
6984
7168
|
gutter.replaceChildren(fragment);
|
|
7169
|
+
gutter.dataset.lineCount = String(visualRows.length);
|
|
7170
|
+
syncVditorLineNumberScroll(editor);
|
|
6985
7171
|
}
|
|
6986
7172
|
|
|
6987
7173
|
function updateVditorWordCount(editor, markdown) {
|
|
@@ -7026,6 +7212,12 @@ function ensureVditorIconScript() {
|
|
|
7026
7212
|
function destroyVditorEditor(editor) {
|
|
7027
7213
|
if (!editor) return;
|
|
7028
7214
|
editor.__attachmentObserver?.disconnect();
|
|
7215
|
+
editor.__vditorLineNumberObserver?.disconnect();
|
|
7216
|
+
editor.__vditorLineNumberResizeObserver?.disconnect();
|
|
7217
|
+
if (editor.__vditorLineNumberSurface && editor.__vditorLineNumberScrollHandler) {
|
|
7218
|
+
editor.__vditorLineNumberSurface.removeEventListener("scroll", editor.__vditorLineNumberScrollHandler);
|
|
7219
|
+
}
|
|
7220
|
+
if (editor.__vditorLineNumberFrame) cancelAnimationFrame(editor.__vditorLineNumberFrame);
|
|
7029
7221
|
const host = editor.vditor?.element;
|
|
7030
7222
|
editor.destroy();
|
|
7031
7223
|
if (host) delete host.__vditor;
|
|
@@ -8715,18 +8907,22 @@ async function showChapterInsight() {
|
|
|
8715
8907
|
if (requestId !== chapterInsightRequestId || state.chapter?.id !== chapterId || $("#editor-view").classList.contains("hidden")) return;
|
|
8716
8908
|
const insight = insights.find((item) => item.chapterVersion === state.chapter.versionNo) ?? insights[0];
|
|
8717
8909
|
const region = $("#toast-region");
|
|
8910
|
+
const chapterTitle = state.chapter.title;
|
|
8718
8911
|
const element = document.createElement("section");
|
|
8719
8912
|
element.id = "chapter-insight-toast";
|
|
8720
8913
|
element.className = "toast chapter-insight-toast";
|
|
8721
8914
|
element.setAttribute("role", "region");
|
|
8722
|
-
element.setAttribute("aria-label",
|
|
8915
|
+
element.setAttribute("aria-label", `章节概览:${chapterTitle}`);
|
|
8723
8916
|
const header = document.createElement("header");
|
|
8724
8917
|
header.className = "chapter-insight-toast-header";
|
|
8725
8918
|
const heading = document.createElement("div");
|
|
8726
8919
|
heading.className = "chapter-insight-toast-heading";
|
|
8727
8920
|
const title = document.createElement("strong");
|
|
8728
8921
|
title.textContent = "章节概览";
|
|
8729
|
-
|
|
8922
|
+
const chapterName = document.createElement("span");
|
|
8923
|
+
chapterName.className = "chapter-insight-toast-chapter-title";
|
|
8924
|
+
chapterName.textContent = chapterTitle;
|
|
8925
|
+
heading.append(title, chapterName);
|
|
8730
8926
|
if (insight && insight.chapterVersion !== state.chapter.versionNo) {
|
|
8731
8927
|
const stale = document.createElement("span");
|
|
8732
8928
|
stale.textContent = `基于旧版本 v${insight.chapterVersion}`;
|
package/dist/public/index.html
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<link rel="icon" href="/icon.svg?v=20260712" type="image/svg+xml">
|
|
11
11
|
<link rel="manifest" href="/site.webmanifest">
|
|
12
12
|
<link rel="stylesheet" href="/vendor/vditor/dist/index.css?v=3.11.2">
|
|
13
|
-
<link rel="stylesheet" href="/styles.css?v=20260729-
|
|
13
|
+
<link rel="stylesheet" href="/styles.css?v=20260729-analysis-rerun-vditor-ui-v1">
|
|
14
14
|
</head>
|
|
15
15
|
<body class="auth-pending">
|
|
16
16
|
<section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
|
|
@@ -876,6 +876,6 @@
|
|
|
876
876
|
<div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
|
|
877
877
|
<script id="vditorIconScript" src="/vendor/vditor/dist/js/icons/ant.js?v=3.11.2"></script>
|
|
878
878
|
<script src="/vendor/vditor/dist/index.min.js?v=3.11.2"></script>
|
|
879
|
-
<script type="module" src="/app.js?v=20260729-
|
|
879
|
+
<script type="module" src="/app.js?v=20260729-analysis-rerun-vditor-ui-v1"></script>
|
|
880
880
|
</body>
|
|
881
881
|
</html>
|
package/dist/public/styles.css
CHANGED
|
@@ -509,7 +509,7 @@ input[type="checkbox"][hidden] { display: none; }
|
|
|
509
509
|
|
|
510
510
|
.left-panel, .ai-panel { position: relative; min-height: 0; background: var(--panel); }
|
|
511
511
|
.left-panel { display: flex; flex-direction: column; border-right: 1px solid var(--line); padding: 0; overflow: hidden; }
|
|
512
|
-
.left-panel-body { flex: 1 1 auto; min-height: 0; padding: 18px
|
|
512
|
+
.left-panel-body { flex: 1 1 auto; min-height: 0; padding: 18px 28px 16px 14px; overflow-y: auto; scrollbar-gutter: stable; }
|
|
513
513
|
.ai-panel { border-left: 1px solid var(--line); display: flex; flex-direction: column; padding: 14px 16px 18px; min-width: 0; }
|
|
514
514
|
.panel-collapse-button { z-index: 8; flex: 0 0 auto; width: 24px; height: 24px; padding: 0; border: 1px solid var(--line); border-radius: 4px; background: var(--panel); color: var(--muted); font-size: 18px; line-height: 1; }
|
|
515
515
|
.ai-heading #ai-panel-toggle { flex-basis: 30px; width: 30px; height: 30px; }
|
|
@@ -1346,6 +1346,8 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1346
1346
|
.task-auto-run-copy { display: grid; grid-column: 1; gap: 4px; }
|
|
1347
1347
|
.task-auto-run-copy strong { font-size: 13px; }
|
|
1348
1348
|
.task-auto-run-copy small { color: var(--muted); font-size: 10px; line-height: 1.5; }
|
|
1349
|
+
.task-auto-run-actions { display: flex; grid-column: 2; grid-row: 1; align-items: start; justify-content: flex-end; min-height: 32px; }
|
|
1350
|
+
.task-auto-run-actions:empty { display: none; }
|
|
1349
1351
|
.task-auto-run-controls {
|
|
1350
1352
|
display: flex;
|
|
1351
1353
|
grid-column: 1;
|
|
@@ -1558,6 +1560,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1558
1560
|
.task-result-json-content textarea { width: 100%; min-height: 360px; max-height: 52vh; resize: vertical; overflow: auto; margin: 0; padding: 10px; border: 1px solid var(--line); border-radius: 4px; background: var(--surface-soft); color: var(--ink); font-family: var(--font-latin), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 10px; line-height: 1.55; tab-size: 2; white-space: pre; }
|
|
1559
1561
|
.task-detail-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-top: 12px; }
|
|
1560
1562
|
.task-detail-actions small { color: var(--muted); }
|
|
1563
|
+
.task-rerun-model-intro { grid-column: 1 / -1; color: var(--muted); }
|
|
1561
1564
|
.relationship-identity-repair-entry { display: flex; align-items: center; justify-content: space-between; gap: 14px; margin-top: 10px; padding: 12px; border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--line)); border-radius: 6px; background: color-mix(in srgb, var(--accent) 7%, var(--surface)); }
|
|
1562
1565
|
.relationship-identity-repair-entry > div { display: grid; gap: 4px; }
|
|
1563
1566
|
.relationship-identity-repair-entry strong { color: var(--ink); }
|
|
@@ -2184,6 +2187,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2184
2187
|
.chapter-insight-toast-heading { display: flex; min-width: 0; align-items: center; gap: 8px; }
|
|
2185
2188
|
.chapter-insight-toast-heading strong { font-size: 12px; }
|
|
2186
2189
|
.chapter-insight-toast-heading span { overflow: hidden; color: var(--accent-dark); font-size: 9px; text-overflow: ellipsis; white-space: nowrap; }
|
|
2190
|
+
.chapter-insight-toast-heading .chapter-insight-toast-chapter-title { min-width: 0; color: var(--ink); font-size: 11px; }
|
|
2187
2191
|
.chapter-insight-toast-close { flex: 0 0 auto; min-height: 28px; padding: 5px 9px; font-size: 10px; }
|
|
2188
2192
|
.chapter-insight-toast-body { display: grid; gap: 10px; min-height: 0; padding: 12px 14px 14px; overflow-y: auto; color: var(--muted); font-size: 11px; line-height: 1.6; overscroll-behavior: contain; }
|
|
2189
2193
|
.chapter-insight-toast-body p { margin: 0; white-space: pre-wrap; }
|
|
@@ -2498,9 +2502,10 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2498
2502
|
.vditor-editor-host .vditor-toolbar { display: flex; align-items: center; flex: 0 0 auto; }
|
|
2499
2503
|
.vditor-editor-host .markdown-word-count { display: inline-flex; align-items: center; align-self: center; min-height: 28px; margin-left: 24px; padding: 0 10px; color: var(--muted); font: 10px/1 var(--font-latin), var(--font-cjk), sans-serif; white-space: nowrap; }
|
|
2500
2504
|
.vditor-editor-host .vditor-content { position: relative; }
|
|
2501
|
-
.vditor-editor-host .vditor-line-numbers { display: none; position: absolute; z-index: 1; inset: 0 auto 0 0; width: 42px; padding:
|
|
2502
|
-
.vditor-editor-host.vditor.show-line-numbers .vditor-line-numbers { display:
|
|
2503
|
-
.vditor-editor-host.vditor
|
|
2505
|
+
.vditor-editor-host .vditor-line-numbers { display: none; position: absolute; z-index: 1; inset: 0 auto 0 0; width: 42px; padding: 0; overflow: hidden; border-right: 1px solid var(--line); background: color-mix(in srgb, var(--surface-soft) 88%, transparent); color: var(--muted); font: 12px/1.75 var(--font-latin), monospace; text-align: right; user-select: none; pointer-events: none; will-change: transform; }
|
|
2506
|
+
.vditor-editor-host.vditor.show-line-numbers .vditor-line-numbers { display: block; }
|
|
2507
|
+
.vditor-editor-host .vditor-line-number { position: absolute; top: 0; right: 8px; left: 0; display: block; box-sizing: border-box; overflow: hidden; padding: 0; text-align: right; white-space: nowrap; }
|
|
2508
|
+
.vditor-editor-host.vditor.show-line-numbers .vditor-ir, .vditor-editor-host.vditor.show-line-numbers .vditor-wysiwyg, .vditor-editor-host.vditor.show-line-numbers .vditor-sv, .vditor-editor-host.vditor.show-line-numbers .vditor-sv textarea { padding-left: 42px; }
|
|
2504
2509
|
.vditor-editor-host .vditor-line-number-button[aria-pressed="true"] { color: var(--accent-dark); background: color-mix(in srgb, var(--accent) 16%, transparent); }
|
|
2505
2510
|
.vditor-editor-host .vditor-content { min-height: 0; }
|
|
2506
2511
|
.vditor-editor-host .vditor-reset { color: var(--ink); font-family: var(--font-cjk), var(--font-latin), sans-serif; }
|
|
@@ -2711,7 +2716,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2711
2716
|
}
|
|
2712
2717
|
|
|
2713
2718
|
@media (max-width: 1100px) {
|
|
2714
|
-
.left-panel-body { padding-inline: 10px; }
|
|
2719
|
+
.left-panel-body { padding-inline: 10px 24px; }
|
|
2715
2720
|
.ai-panel { padding-inline: 14px; }
|
|
2716
2721
|
.editor-toolbar { grid-template-areas: "path" "title" "actions"; grid-template-columns: minmax(0, 1fr); gap: 12px; padding-inline: 6%; }
|
|
2717
2722
|
.editor-actions { justify-content: flex-end; flex-wrap: wrap; }
|
|
@@ -2725,7 +2730,9 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2725
2730
|
.top-actions .ghost-button:not(.settings-button) { display: none; }
|
|
2726
2731
|
.book-shelf { grid-template-columns: repeat(3, minmax(180px, 1fr)); }
|
|
2727
2732
|
.task-auto-run-panel { grid-template-columns: minmax(0, 1fr); column-gap: 0; }
|
|
2728
|
-
.task-auto-run-copy, .task-auto-run-
|
|
2733
|
+
.task-auto-run-copy, .task-auto-run-actions, .task-auto-run-controls, .task-auto-run-meta { grid-column: 1; }
|
|
2734
|
+
.task-auto-run-actions { grid-row: auto; }
|
|
2735
|
+
.task-auto-run-help, .task-auto-run-alert { grid-column: 1; }
|
|
2729
2736
|
.task-auto-run-progress { grid-column: 1; grid-row: auto; justify-self: stretch; width: 100%; }
|
|
2730
2737
|
.task-auto-run-progress-ring { display: none; }
|
|
2731
2738
|
.task-auto-run-progress-bar-layout { display: grid; gap: 6px; width: 100%; }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type VditorLineRect = {
|
|
2
|
+
top: number;
|
|
3
|
+
height: number;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type VditorLineNumberRow = VditorLineRect & {
|
|
7
|
+
blank: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function normalizeVditorLineRects(rects: VditorLineRect[], tolerance?: number): VditorLineRect[];
|
|
11
|
+
export function buildVditorLineNumberRows(
|
|
12
|
+
rects: VditorLineRect[],
|
|
13
|
+
options?: { lineHeight?: number; blankLineCount?: number }
|
|
14
|
+
): VditorLineNumberRow[];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export function normalizeVditorLineRects(rects, tolerance = 1.5) {
|
|
2
|
+
const rows = [];
|
|
3
|
+
const sorted = rects
|
|
4
|
+
.filter((rect) => Number.isFinite(Number(rect.top)) && Number.isFinite(Number(rect.height)) && Number(rect.height) > 0)
|
|
5
|
+
.map((rect) => ({ top: Number(rect.top), height: Number(rect.height) }))
|
|
6
|
+
.sort((left, right) => left.top - right.top);
|
|
7
|
+
for (const rect of sorted) {
|
|
8
|
+
const current = rows[rows.length - 1];
|
|
9
|
+
if (current && Math.abs(current.top - rect.top) <= tolerance) {
|
|
10
|
+
current.height = Math.max(current.height, rect.height);
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
rows.push(rect);
|
|
14
|
+
}
|
|
15
|
+
return rows;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function buildVditorLineNumberRows(rects, { lineHeight = 0, blankLineCount = 0 } = {}) {
|
|
19
|
+
const normalizedLineHeight = Number(lineHeight) > 0 ? Number(lineHeight) : 0;
|
|
20
|
+
const normalizedBlankLineCount = Math.max(0, Math.floor(Number(blankLineCount) || 0));
|
|
21
|
+
const visualRows = normalizeVditorLineRects(rects);
|
|
22
|
+
if (visualRows.length === 0) {
|
|
23
|
+
return normalizedBlankLineCount > 0
|
|
24
|
+
? Array.from({ length: normalizedBlankLineCount }, (_, index) => ({ top: index * normalizedLineHeight, height: normalizedLineHeight, blank: true }))
|
|
25
|
+
: normalizedLineHeight > 0 ? [{ top: 0, height: normalizedLineHeight, blank: true }] : [];
|
|
26
|
+
}
|
|
27
|
+
const rows = [];
|
|
28
|
+
let remainingBlankLines = normalizedBlankLineCount;
|
|
29
|
+
for (const visualRow of visualRows) {
|
|
30
|
+
const height = normalizedLineHeight || visualRow.height;
|
|
31
|
+
const top = visualRow.top - (height - visualRow.height) / 2;
|
|
32
|
+
const previous = rows[rows.length - 1];
|
|
33
|
+
if (previous && remainingBlankLines > 0 && normalizedLineHeight > 0) {
|
|
34
|
+
const availableSlots = Math.max(0, Math.round((top - previous.top) / normalizedLineHeight) - 1);
|
|
35
|
+
const blankLines = Math.min(remainingBlankLines, availableSlots);
|
|
36
|
+
for (let index = 1; index <= blankLines; index += 1) {
|
|
37
|
+
rows.push({ top: previous.top + normalizedLineHeight * index, height: normalizedLineHeight, blank: true });
|
|
38
|
+
}
|
|
39
|
+
remainingBlankLines -= blankLines;
|
|
40
|
+
}
|
|
41
|
+
rows.push({ top, height, blank: false });
|
|
42
|
+
}
|
|
43
|
+
const last = rows[rows.length - 1];
|
|
44
|
+
let nextTop = last.top;
|
|
45
|
+
while (remainingBlankLines > 0 && normalizedLineHeight > 0) {
|
|
46
|
+
nextTop += normalizedLineHeight;
|
|
47
|
+
rows.push({ top: nextTop, height: normalizedLineHeight, blank: true });
|
|
48
|
+
remainingBlankLines -= 1;
|
|
49
|
+
}
|
|
50
|
+
return rows;
|
|
51
|
+
}
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const APP_VERSION = "0.5.
|
|
1
|
+
export const APP_VERSION = "0.5.10";
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/dist/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC"}
|