@musnows/scriverse 0.9.6 → 0.9.8
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 +11 -9
- package/dist/ai.js.map +1 -1
- package/dist/app.js +6 -6
- package/dist/app.js.map +1 -1
- package/dist/public/ai-skill-menu.js +42 -0
- package/dist/public/ai-usage.d.ts +5 -2
- package/dist/public/ai-usage.js +50 -37
- package/dist/public/app.js +113 -35
- package/dist/public/index.html +3 -3
- package/dist/public/markdown.js +4 -0
- package/dist/public/styles.css +28 -3
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const AI_SKILL_COMMAND_OPTIONS = Object.freeze([
|
|
2
|
+
Object.freeze({
|
|
3
|
+
name: "continue-writing",
|
|
4
|
+
label: "续写正文",
|
|
5
|
+
description: "延续当前章节,并保持情节、人物与设定一致。"
|
|
6
|
+
}),
|
|
7
|
+
Object.freeze({
|
|
8
|
+
name: "polish-writing",
|
|
9
|
+
label: "润色选中文本",
|
|
10
|
+
description: "润色当前章节的精确选区,并生成可确认的替换建议。"
|
|
11
|
+
})
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
export function findAiSkillCommand(value, cursor = String(value ?? "").length) {
|
|
15
|
+
const text = String(value ?? "");
|
|
16
|
+
const safeCursor = Math.max(0, Math.min(Number(cursor) || 0, text.length));
|
|
17
|
+
const match = text.slice(0, safeCursor).match(/(?:^|\s)(\/([^/\s]*))$/u);
|
|
18
|
+
if (!match) return null;
|
|
19
|
+
return {
|
|
20
|
+
start: safeCursor - match[1].length,
|
|
21
|
+
end: safeCursor,
|
|
22
|
+
query: match[2]
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function listAiSkillOptions(query = "") {
|
|
27
|
+
const keyword = String(query).trim().toLocaleLowerCase("zh-CN");
|
|
28
|
+
if (!keyword) return [...AI_SKILL_COMMAND_OPTIONS];
|
|
29
|
+
return AI_SKILL_COMMAND_OPTIONS.filter((skill) => [skill.name, skill.label]
|
|
30
|
+
.some((value) => value.toLocaleLowerCase("zh-CN").includes(keyword)));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function applyAiSkillCommand(value, match, skillName) {
|
|
34
|
+
const text = String(value ?? "");
|
|
35
|
+
const command = `/${String(skillName).trim()}`;
|
|
36
|
+
const separator = /^\s/u.test(text.slice(match.end)) ? "" : " ";
|
|
37
|
+
return {
|
|
38
|
+
text: `${text.slice(0, match.start)}${command}${separator}${text.slice(match.end)}`,
|
|
39
|
+
command,
|
|
40
|
+
cursor: match.start + command.length + separator.length
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type AiUsageCalendarCell = {
|
|
2
2
|
date: string;
|
|
3
3
|
totalTokens: number;
|
|
4
|
+
outsideYear: boolean;
|
|
4
5
|
future: boolean;
|
|
5
6
|
week: number;
|
|
6
7
|
weekday: number;
|
|
@@ -10,12 +11,14 @@ export type AiUsageCalendarCell = {
|
|
|
10
11
|
export function formatTokenCount(value: unknown): string;
|
|
11
12
|
export function formatCacheHitRate(value: unknown): string;
|
|
12
13
|
export function formatEstimatedCost(value: unknown): string;
|
|
14
|
+
export function usageCalendarYears(daily: Array<{ date: string; totalTokens: number }> | unknown): number[];
|
|
13
15
|
export function buildUsageCalendar(
|
|
14
16
|
daily: Array<{ date: string; totalTokens: number }> | unknown,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
year: number,
|
|
18
|
+
todayDateKey?: string
|
|
17
19
|
): {
|
|
18
20
|
cells: AiUsageCalendarCell[];
|
|
19
21
|
months: Array<{ week: number; label: string }>;
|
|
20
22
|
weekCount: number;
|
|
23
|
+
year: number | null;
|
|
21
24
|
};
|
package/dist/public/ai-usage.js
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
return
|
|
1
|
+
const usageDateKeyPattern = /^(\d{4})-(\d{2})-(\d{2})$/u;
|
|
2
|
+
|
|
3
|
+
function usageDateFromKey(value) {
|
|
4
|
+
const match = usageDateKeyPattern.exec(String(value));
|
|
5
|
+
if (!match) return null;
|
|
6
|
+
const year = Number(match[1]);
|
|
7
|
+
const month = Number(match[2]);
|
|
8
|
+
const day = Number(match[3]);
|
|
9
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
10
|
+
return year >= 1_000 && date.toISOString().slice(0, 10) === String(value) ? date : null;
|
|
6
11
|
}
|
|
7
12
|
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
date.setHours(0, 0, 0, 0);
|
|
11
|
-
return date;
|
|
13
|
+
function usageDateKey(date) {
|
|
14
|
+
return date.toISOString().slice(0, 10);
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
export function formatTokenCount(value) {
|
|
15
18
|
const count = Math.max(0, Math.round(Number(value) || 0));
|
|
16
19
|
if (count < 10_000) return count.toLocaleString("zh-CN");
|
|
20
|
+
if (count >= 100_000_000) return `${(count / 100_000_000).toFixed(2)}亿`;
|
|
17
21
|
return new Intl.NumberFormat("zh-CN", {
|
|
18
22
|
notation: "compact",
|
|
19
23
|
compactDisplay: "short",
|
|
@@ -38,26 +42,43 @@ export function formatEstimatedCost(value) {
|
|
|
38
42
|
})}`;
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
export function
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
export function usageCalendarYears(daily) {
|
|
46
|
+
return [...new Set((Array.isArray(daily) ? daily : [])
|
|
47
|
+
.filter((item) => usageDateFromKey(item?.date) && Math.max(0, Number(item?.totalTokens) || 0) > 0)
|
|
48
|
+
.map((item) => Number(String(item.date).slice(0, 4))))]
|
|
49
|
+
.sort((left, right) => right - left);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function buildUsageCalendar(daily, year, todayDateKey) {
|
|
53
|
+
const selectedYear = Number.isInteger(Number(year)) ? Number(year) : usageCalendarYears(daily)[0];
|
|
54
|
+
if (!selectedYear || selectedYear < 1_000 || selectedYear > 9_999) {
|
|
55
|
+
return { cells: [], months: [], weekCount: 0, year: null };
|
|
56
|
+
}
|
|
57
|
+
const firstDay = new Date(Date.UTC(selectedYear, 0, 1));
|
|
58
|
+
const lastDay = new Date(Date.UTC(selectedYear, 11, 31));
|
|
59
|
+
const start = new Date(firstDay);
|
|
60
|
+
start.setUTCDate(start.getUTCDate() - start.getUTCDay());
|
|
61
|
+
const end = new Date(lastDay);
|
|
62
|
+
end.setUTCDate(end.getUTCDate() + (6 - end.getUTCDay()));
|
|
63
|
+
const weekCount = Math.floor((end.getTime() - start.getTime()) / (7 * 24 * 60 * 60_000)) + 1;
|
|
64
|
+
const normalizedTodayKey = usageDateFromKey(todayDateKey) ? String(todayDateKey) : null;
|
|
65
|
+
const usageByDate = new Map((Array.isArray(daily) ? daily : [])
|
|
66
|
+
.filter((item) => usageDateFromKey(item?.date)?.getUTCFullYear() === selectedYear)
|
|
67
|
+
.map((item) => [String(item.date), Math.max(0, Number(item.totalTokens) || 0)]));
|
|
49
68
|
const visibleValues = [];
|
|
50
69
|
const cells = [];
|
|
51
|
-
for (let index = 0; index <
|
|
70
|
+
for (let index = 0; index < weekCount * 7; index += 1) {
|
|
52
71
|
const date = new Date(start);
|
|
53
|
-
date.
|
|
54
|
-
const dateKey =
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
|
|
72
|
+
date.setUTCDate(start.getUTCDate() + index);
|
|
73
|
+
const dateKey = usageDateKey(date);
|
|
74
|
+
const outsideYear = date.getUTCFullYear() !== selectedYear;
|
|
75
|
+
const totalTokens = outsideYear ? 0 : usageByDate.get(dateKey) ?? 0;
|
|
76
|
+
const future = !outsideYear && normalizedTodayKey !== null && dateKey > normalizedTodayKey;
|
|
77
|
+
if (!outsideYear && !future && totalTokens > 0) visibleValues.push(totalTokens);
|
|
58
78
|
cells.push({
|
|
59
79
|
date: dateKey,
|
|
60
80
|
totalTokens,
|
|
81
|
+
outsideYear,
|
|
61
82
|
future,
|
|
62
83
|
week: Math.floor(index / 7),
|
|
63
84
|
weekday: index % 7,
|
|
@@ -66,21 +87,13 @@ export function buildUsageCalendar(daily, today = new Date(), weekCount = 53) {
|
|
|
66
87
|
}
|
|
67
88
|
const maximum = Math.max(0, ...visibleValues);
|
|
68
89
|
for (const cell of cells) {
|
|
69
|
-
cell.level = cell.future || cell.totalTokens <= 0 || maximum <= 0
|
|
90
|
+
cell.level = cell.outsideYear || cell.future || cell.totalTokens <= 0 || maximum <= 0
|
|
70
91
|
? 0
|
|
71
92
|
: Math.max(1, Math.min(4, Math.ceil(Math.sqrt(cell.totalTokens / maximum) * 4)));
|
|
72
93
|
}
|
|
73
|
-
const months =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (week === 0 || previous?.getMonth() !== date.getMonth()) {
|
|
79
|
-
months.push({
|
|
80
|
-
week,
|
|
81
|
-
label: new Intl.DateTimeFormat("zh-CN", { month: "short" }).format(date)
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return { cells, months, weekCount: Math.max(1, weekCount) };
|
|
94
|
+
const months = Array.from({ length: 12 }, (_, month) => ({
|
|
95
|
+
week: Math.floor((Date.UTC(selectedYear, month, 1) - start.getTime()) / (7 * 24 * 60 * 60_000)),
|
|
96
|
+
label: `${month + 1}月`
|
|
97
|
+
}));
|
|
98
|
+
return { cells, months, weekCount, year: selectedYear };
|
|
86
99
|
}
|
package/dist/public/app.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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
|
-
import { renderMarkdown } from "/markdown.js?v=
|
|
3
|
+
import { renderMarkdown } from "/markdown.js?v=20260830-adjacent-blockquotes-v1";
|
|
4
4
|
import { findAiMention, listAiMentionOptions, mergeAiReferenceScope, userMessageMentionNames } from "/ai-mentions.js?v=20260811-user-message-mentions-v1";
|
|
5
|
+
import { applyAiSkillCommand, findAiSkillCommand, listAiSkillOptions } from "/ai-skill-menu.js?v=20260830-ai-skill-slash-menu-v1";
|
|
5
6
|
import {
|
|
6
7
|
emptyRoleplayScenePin,
|
|
7
8
|
normalizeRoleplayScenePin,
|
|
@@ -31,7 +32,7 @@ import { shouldSendAiPrompt } from "/ai-prompt-keyboard.js?v=20260713-enter-to-s
|
|
|
31
32
|
import { estimateAiMessageTokens, formatAiMessageMeta } from "/ai-message-meta.js?v=20260814-ai-model-lock-v1";
|
|
32
33
|
import { createStreamTypewriter, createStreamTypewriterSpeedController } from "/stream-typewriter.js?v=20260818-ai-agent-turn-process-v1";
|
|
33
34
|
import { assertAiStreamCompleted, readAiEventStream } from "/ai-stream-protocol.js?v=20260812-ai-stream-complete-v1";
|
|
34
|
-
import { buildUsageCalendar, formatCacheHitRate, formatEstimatedCost, formatTokenCount } from "/ai-usage.js?v=
|
|
35
|
+
import { buildUsageCalendar, formatCacheHitRate, formatEstimatedCost, formatTokenCount, usageCalendarYears } from "/ai-usage.js?v=20260830-ai-usage-year-v1";
|
|
35
36
|
import { formatAiMessageTime } from "/ai-message-time.js?v=20260801-month-day-time";
|
|
36
37
|
import { formatAiContextUsagePercent, formatAiContextUsageTooltip, mergeAiContextUsage, normalizeAiContextTokenDistribution, resolveAiContextUsage } from "/ai-context-meter.js?v=20260828-context-output-usage-v1";
|
|
37
38
|
import { isPhoneClient } from "/phone-client.js?v=20260819-phone-client-v1";
|
|
@@ -1526,6 +1527,7 @@ let moduleNavExpanded = false;
|
|
|
1526
1527
|
const chapterAutoSaveDelay = 800;
|
|
1527
1528
|
const chapterLineInputRenderDelay = 32;
|
|
1528
1529
|
let aiMentionMatch = null;
|
|
1530
|
+
let aiSkillMatch = null;
|
|
1529
1531
|
let aiMentionRange = null;
|
|
1530
1532
|
let aiMentionActiveIndex = -1;
|
|
1531
1533
|
let settingsReturnContext = null;
|
|
@@ -2705,7 +2707,7 @@ function resetAiFeed(
|
|
|
2705
2707
|
const roleplayUserName = roleplayUserCharacter?.name;
|
|
2706
2708
|
feed.innerHTML = roleplayName
|
|
2707
2709
|
? `<div class="assistant-message"><span class="message-heading"><span>${esc(roleplayName)}</span></span><div class="message-body"><p>正在扮演 ${esc(roleplayName)}。${roleplayUserName ? `你将以 ${esc(roleplayUserName)} 的身份与我互动。` : "我可以通过角色卡、人物关系、知情设定和故事正文回答。"}</p></div></div>`
|
|
2708
|
-
: '<div class="assistant-message"><span class="message-heading"><span>助手</span></span><div class="message-body"><p
|
|
2710
|
+
: '<div class="assistant-message"><span class="message-heading"><span>助手</span></span><div class="message-body"><p>选择章节和模型后即可开始问答;所有引用都基于已保存正文。</p></div></div>';
|
|
2709
2711
|
}
|
|
2710
2712
|
|
|
2711
2713
|
function aiAssistantLabel(suffix = "", roleplayCharacter = state.aiRoleplayCharacter) {
|
|
@@ -4887,7 +4889,7 @@ async function addAiImageFiles(files) {
|
|
|
4887
4889
|
persistActiveAiChatTab();
|
|
4888
4890
|
}
|
|
4889
4891
|
|
|
4890
|
-
function clearAiPromptComposer() {
|
|
4892
|
+
function clearAiPromptComposer({ collapseScenePanel = false } = {}) {
|
|
4891
4893
|
state.aiCitations = [];
|
|
4892
4894
|
state.aiReferences = [];
|
|
4893
4895
|
state.aiImageAttachments = [];
|
|
@@ -4899,6 +4901,7 @@ function clearAiPromptComposer() {
|
|
|
4899
4901
|
renderAiSemanticInjection();
|
|
4900
4902
|
hideAiMentionMenu();
|
|
4901
4903
|
syncAiSceneComposer();
|
|
4904
|
+
if (collapseScenePanel) setAiScenePanelExpanded(false);
|
|
4902
4905
|
}
|
|
4903
4906
|
|
|
4904
4907
|
function captureAiPromptComposer() {
|
|
@@ -4963,6 +4966,15 @@ function roleplaySceneComposerVisible() {
|
|
|
4963
4966
|
return $("#ai-task").value === "roleplay" && Boolean(state.aiRoleplayCharacter);
|
|
4964
4967
|
}
|
|
4965
4968
|
|
|
4969
|
+
function setAiScenePanelExpanded(expanded) {
|
|
4970
|
+
const button = $("#ai-scene-button");
|
|
4971
|
+
const panel = $("#ai-scene-panel");
|
|
4972
|
+
if (!button || !panel) return;
|
|
4973
|
+
const nextExpanded = Boolean(expanded && !button.classList.contains("hidden"));
|
|
4974
|
+
panel.classList.toggle("hidden", !nextExpanded);
|
|
4975
|
+
button.setAttribute("aria-expanded", String(nextExpanded));
|
|
4976
|
+
}
|
|
4977
|
+
|
|
4966
4978
|
function syncAiSceneComposer() {
|
|
4967
4979
|
const button = $("#ai-scene-button");
|
|
4968
4980
|
const panel = $("#ai-scene-panel");
|
|
@@ -4973,10 +4985,7 @@ function syncAiSceneComposer() {
|
|
|
4973
4985
|
button.classList.toggle("hidden", !visible);
|
|
4974
4986
|
button.disabled = !visible || busy || readOnly;
|
|
4975
4987
|
button.setAttribute("aria-hidden", String(!visible));
|
|
4976
|
-
if (!visible)
|
|
4977
|
-
panel.classList.add("hidden");
|
|
4978
|
-
button.setAttribute("aria-expanded", "false");
|
|
4979
|
-
}
|
|
4988
|
+
if (!visible) setAiScenePanelExpanded(false);
|
|
4980
4989
|
const hasContent = Boolean(aiSceneDirectionText().trim()) || roleplayScenePinHasContent(captureAiScenePin());
|
|
4981
4990
|
button.classList.toggle("is-active", hasContent);
|
|
4982
4991
|
}
|
|
@@ -4986,8 +4995,7 @@ function toggleAiScenePanel() {
|
|
|
4986
4995
|
const panel = $("#ai-scene-panel");
|
|
4987
4996
|
if (!button || !panel || button.classList.contains("hidden")) return;
|
|
4988
4997
|
const willOpen = panel.classList.contains("hidden");
|
|
4989
|
-
|
|
4990
|
-
button.setAttribute("aria-expanded", String(willOpen));
|
|
4998
|
+
setAiScenePanelExpanded(willOpen);
|
|
4991
4999
|
if (willOpen) $("#ai-scene-direction")?.focus();
|
|
4992
5000
|
}
|
|
4993
5001
|
|
|
@@ -5030,6 +5038,7 @@ function aiPromptTextBoundary(prompt, offset) {
|
|
|
5030
5038
|
|
|
5031
5039
|
function hideAiMentionMenu() {
|
|
5032
5040
|
aiMentionMatch = null;
|
|
5041
|
+
aiSkillMatch = null;
|
|
5033
5042
|
aiMentionRange = null;
|
|
5034
5043
|
aiMentionActiveIndex = -1;
|
|
5035
5044
|
const prompt = $("#ai-prompt");
|
|
@@ -5086,21 +5095,37 @@ function syncAiReferencesWithPrompt() {
|
|
|
5086
5095
|
function updateAiMentionMenu() {
|
|
5087
5096
|
syncAiReferencesWithPrompt();
|
|
5088
5097
|
const prompt = $("#ai-prompt");
|
|
5089
|
-
const
|
|
5090
|
-
|
|
5098
|
+
const textBeforeCursor = aiPromptTextBeforeCursor();
|
|
5099
|
+
const skillMatch = $("#ai-task").value === "roleplay" ? null : findAiSkillCommand(textBeforeCursor);
|
|
5100
|
+
const mentionMatch = skillMatch ? null : findAiMention(textBeforeCursor);
|
|
5101
|
+
if (!skillMatch && !mentionMatch) return hideAiMentionMenu();
|
|
5091
5102
|
const selection = window.getSelection();
|
|
5092
5103
|
if (!selection?.rangeCount || !prompt.contains(selection.anchorNode)) return hideAiMentionMenu();
|
|
5093
|
-
|
|
5104
|
+
aiSkillMatch = skillMatch;
|
|
5105
|
+
aiMentionMatch = mentionMatch;
|
|
5094
5106
|
aiMentionRange = selection.getRangeAt(0).cloneRange();
|
|
5095
5107
|
const menu = $("#ai-mention-menu");
|
|
5108
|
+
if (skillMatch) {
|
|
5109
|
+
const options = listAiSkillOptions(skillMatch.query);
|
|
5110
|
+
aiMentionActiveIndex = -1;
|
|
5111
|
+
prompt.removeAttribute("aria-activedescendant");
|
|
5112
|
+
menu.setAttribute("aria-label", "选择写作 Skill");
|
|
5113
|
+
menu.innerHTML = options.length
|
|
5114
|
+
? options.map((item, index) => `<button id="ai-skill-option-${index}" class="ai-mention-option ai-skill-option" type="button" role="option" aria-selected="false" tabindex="-1" data-ai-skill-name="${esc(item.name)}"><small>Skill</small><span><strong>/${esc(item.name)}</strong><em>${esc(item.label)} · ${esc(item.description)}</em></span></button>`).join("")
|
|
5115
|
+
: '<p class="ai-mention-empty">没有匹配的写作 Skill</p>';
|
|
5116
|
+
menu.classList.remove("hidden");
|
|
5117
|
+
prompt.setAttribute("aria-expanded", "true");
|
|
5118
|
+
return;
|
|
5119
|
+
}
|
|
5096
5120
|
const chapters = state.work?.volumes.flatMap((volume) => volume.chapters.map((chapter) => ({
|
|
5097
5121
|
...chapter,
|
|
5098
5122
|
volumeTitle: volume.title
|
|
5099
5123
|
}))) ?? [];
|
|
5100
|
-
const options = listAiMentionOptions(state.characters, state.settings, chapters,
|
|
5124
|
+
const options = listAiMentionOptions(state.characters, state.settings, chapters, mentionMatch.query)
|
|
5101
5125
|
.filter((item) => item.kind !== "context-settings" || $("#ai-task").value !== "roleplay");
|
|
5102
5126
|
aiMentionActiveIndex = -1;
|
|
5103
5127
|
prompt.removeAttribute("aria-activedescendant");
|
|
5128
|
+
menu.setAttribute("aria-label", "引用角色、设定、章节或上下文能力");
|
|
5104
5129
|
menu.innerHTML = options.length
|
|
5105
5130
|
? options.map((item, index) => `<button id="ai-mention-option-${index}" class="ai-mention-option" type="button" role="option" aria-selected="false" tabindex="-1" data-ai-reference-kind="${esc(item.kind)}" data-ai-reference-id="${esc(item.id)}" data-ai-reference-name="${esc(item.name)}"><small>${esc(item.kindLabel)}</small><strong>${esc(item.name)}</strong></button>`).join("")
|
|
5106
5131
|
: '<p class="ai-mention-empty">没有匹配的角色、设定、章节或上下文能力</p>';
|
|
@@ -5141,6 +5166,30 @@ function selectAiMention(button) {
|
|
|
5141
5166
|
hideAiMentionMenu();
|
|
5142
5167
|
}
|
|
5143
5168
|
|
|
5169
|
+
function selectAiSkill(button) {
|
|
5170
|
+
if (!aiSkillMatch || !aiMentionRange) return;
|
|
5171
|
+
const prompt = $("#ai-prompt");
|
|
5172
|
+
const cursorText = aiPromptTextFromRange(aiMentionRange, prompt);
|
|
5173
|
+
const localSkill = findAiSkillCommand(cursorText);
|
|
5174
|
+
if (!localSkill) return hideAiMentionMenu();
|
|
5175
|
+
const applied = applyAiSkillCommand(cursorText, localSkill, button.dataset.aiSkillName);
|
|
5176
|
+
const range = document.createRange();
|
|
5177
|
+
const startBoundary = aiPromptTextBoundary(prompt, localSkill.start);
|
|
5178
|
+
const endBoundary = aiPromptTextBoundary(prompt, cursorText.length);
|
|
5179
|
+
range.setStart(startBoundary.node, startBoundary.offset);
|
|
5180
|
+
range.setEnd(endBoundary.node, endBoundary.offset);
|
|
5181
|
+
range.deleteContents();
|
|
5182
|
+
const command = document.createTextNode(`${applied.command} `);
|
|
5183
|
+
range.insertNode(command);
|
|
5184
|
+
const selection = window.getSelection();
|
|
5185
|
+
selection?.removeAllRanges();
|
|
5186
|
+
range.setStartAfter(command);
|
|
5187
|
+
range.collapse(true);
|
|
5188
|
+
selection?.addRange(range);
|
|
5189
|
+
prompt.focus();
|
|
5190
|
+
hideAiMentionMenu();
|
|
5191
|
+
}
|
|
5192
|
+
|
|
5144
5193
|
function addSelectedLinesAsCitation() {
|
|
5145
5194
|
if (!state.chapter || !chapterLineSelection) return;
|
|
5146
5195
|
const selection = selectedChapterLinePayload(chapterLineSelection.start, chapterLineSelection.end);
|
|
@@ -13301,20 +13350,22 @@ async function renderPlatformAiConfig() {
|
|
|
13301
13350
|
}
|
|
13302
13351
|
|
|
13303
13352
|
function tokenUsageDateLabel(date) {
|
|
13353
|
+
const [year, month, day] = String(date).split("-").map(Number);
|
|
13304
13354
|
return new Intl.DateTimeFormat("zh-CN", {
|
|
13305
13355
|
year: "numeric",
|
|
13306
13356
|
month: "short",
|
|
13307
13357
|
day: "numeric",
|
|
13308
|
-
weekday: "short"
|
|
13309
|
-
|
|
13358
|
+
weekday: "short",
|
|
13359
|
+
timeZone: "UTC"
|
|
13360
|
+
}).format(new Date(Date.UTC(year, month - 1, day)));
|
|
13310
13361
|
}
|
|
13311
13362
|
|
|
13312
|
-
function tokenUsageCalendarMarkup(daily) {
|
|
13313
|
-
const calendar = buildUsageCalendar(daily);
|
|
13363
|
+
function tokenUsageCalendarMarkup(daily, year, serverDate) {
|
|
13364
|
+
const calendar = buildUsageCalendar(daily, year, serverDate);
|
|
13314
13365
|
const cells = calendar.cells.map((cell) => {
|
|
13315
13366
|
const label = `${tokenUsageDateLabel(cell.date)}:${Number(cell.totalTokens).toLocaleString("zh-CN")} Token`;
|
|
13316
|
-
return cell.future
|
|
13317
|
-
? `<span class="usage-calendar-cell is-future" data-level="${cell.level}" role="gridcell" aria-disabled="true"></span>`
|
|
13367
|
+
return cell.outsideYear || cell.future
|
|
13368
|
+
? `<span class="usage-calendar-cell ${cell.outsideYear ? "is-outside-year" : "is-future"}" data-level="${cell.level}" role="gridcell" aria-disabled="true"></span>`
|
|
13318
13369
|
: `<button class="usage-calendar-cell" type="button" data-level="${cell.level}" data-usage-calendar-label="${esc(label)}" role="gridcell" aria-label="${esc(label)}"></button>`;
|
|
13319
13370
|
}).join("");
|
|
13320
13371
|
const months = calendar.months.map((month) => `<span style="grid-column:${month.week + 1}">${esc(month.label)}</span>`).join("");
|
|
@@ -13324,7 +13375,7 @@ function tokenUsageCalendarMarkup(daily) {
|
|
|
13324
13375
|
<div class="usage-calendar-months" aria-hidden="true">${months}</div>
|
|
13325
13376
|
<div class="usage-calendar-body">
|
|
13326
13377
|
<div class="usage-calendar-weekdays" aria-hidden="true"><span>一</span><span>三</span><span>五</span></div>
|
|
13327
|
-
<div class="usage-calendar-grid" role="grid" aria-label="
|
|
13378
|
+
<div class="usage-calendar-grid" role="grid" aria-label="${calendar.year} 年每日 Token 用量">${cells}</div>
|
|
13328
13379
|
</div>
|
|
13329
13380
|
</div>
|
|
13330
13381
|
</div>
|
|
@@ -13333,6 +13384,21 @@ function tokenUsageCalendarMarkup(daily) {
|
|
|
13333
13384
|
<div class="usage-calendar-legend"><span>少</span>${[0, 1, 2, 3, 4].map((level) => `<i data-level="${level}" aria-hidden="true"></i>`).join("")}<span>多</span></div>`;
|
|
13334
13385
|
}
|
|
13335
13386
|
|
|
13387
|
+
function bindUsageCalendar(root, usage) {
|
|
13388
|
+
root.querySelectorAll("[data-usage-calendar-year]").forEach((select) => {
|
|
13389
|
+
select.addEventListener("change", () => {
|
|
13390
|
+
const section = select.closest(".usage-calendar-section");
|
|
13391
|
+
const calendarHost = section?.querySelector("[data-usage-calendar-host]");
|
|
13392
|
+
if (!calendarHost) return;
|
|
13393
|
+
calendarHost.innerHTML = tokenUsageCalendarMarkup(usage?.daily, Number(select.value), usage?.serverDate);
|
|
13394
|
+
bindUsageCalendarInteractions(calendarHost);
|
|
13395
|
+
scrollUsageCalendarsToLatest(calendarHost);
|
|
13396
|
+
});
|
|
13397
|
+
});
|
|
13398
|
+
bindUsageCalendarInteractions(root);
|
|
13399
|
+
scrollUsageCalendarsToLatest(root);
|
|
13400
|
+
}
|
|
13401
|
+
|
|
13336
13402
|
function bindUsageCalendarInteractions(root) {
|
|
13337
13403
|
root.querySelectorAll(".usage-calendar-widget").forEach((widget) => {
|
|
13338
13404
|
const tooltip = widget.querySelector(".usage-calendar-tooltip");
|
|
@@ -13556,6 +13622,15 @@ function tokenUsageOverviewMarkup(usage, { title, description, showWorks = false
|
|
|
13556
13622
|
const callTypeUsage = (Array.isArray(usage?.callTypes) ? usage.callTypes : [])
|
|
13557
13623
|
.map((item) => `<span class="usage-call-type-chip"><strong>${esc(callTypeLabels[item.callType] ?? item.callType)}</strong><span>${esc(formatTokenCount(item.totalTokens))} Token · ${Number(item.requestCount || 0).toLocaleString("zh-CN")} 次</span></span>`)
|
|
13558
13624
|
.join("");
|
|
13625
|
+
const calendarYears = usageCalendarYears(usage?.daily);
|
|
13626
|
+
const selectedCalendarYear = calendarYears[0] ?? null;
|
|
13627
|
+
const usageTimezone = String(usage?.timezone || "服务器本地时区");
|
|
13628
|
+
const calendarYearSelect = selectedCalendarYear === null
|
|
13629
|
+
? ""
|
|
13630
|
+
: `<select class="usage-calendar-year-select" data-usage-calendar-year aria-label="每日用量年份">${calendarYears.map((year) => `<option value="${year}">${year} 年</option>`).join("")}</select>`;
|
|
13631
|
+
const calendarMarkup = selectedCalendarYear === null
|
|
13632
|
+
? '<p class="usage-calendar-empty">尚无每日 Token 用量记录。</p>'
|
|
13633
|
+
: tokenUsageCalendarMarkup(usage?.daily, selectedCalendarYear, usage?.serverDate);
|
|
13559
13634
|
return `<section class="usage-overview" aria-labelledby="${showWorks ? "platform-usage-overview-title" : "work-usage-overview-title"}">
|
|
13560
13635
|
<div class="config-section-header usage-overview-header"><div><h2 id="${showWorks ? "platform-usage-overview-title" : "work-usage-overview-title"}">${esc(title || "Token 用量")}</h2><p>${esc(description || "统计该范围内的全部 AI 调用。")}</p></div><button class="ghost-button usage-details-button" type="button" data-token-usage-details aria-haspopup="dialog" aria-expanded="false" aria-controls="token-usage-details-toast">详细数据</button></div>
|
|
13561
13636
|
<div class="usage-stat-grid">
|
|
@@ -13567,8 +13642,8 @@ function tokenUsageOverviewMarkup(usage, { title, description, showWorks = false
|
|
|
13567
13642
|
<p class="usage-measurement-note">${requestCount.toLocaleString("zh-CN")} 次有用量记录的调用。${esc(estimateNote)} 有 ${unpricedModelCount.toLocaleString("zh-CN")} 个模型在价格表中未找到对应价格</p>
|
|
13568
13643
|
${callTypeUsage ? `<div class="usage-call-types" aria-label="按调用类型区分的 Token 用量">${callTypeUsage}</div>` : ""}
|
|
13569
13644
|
<section class="usage-calendar-section" aria-labelledby="${showWorks ? "platform-usage-calendar-title" : "work-usage-calendar-title"}">
|
|
13570
|
-
<header><div><h3 id="${showWorks ? "platform-usage-calendar-title" : "work-usage-calendar-title"}">每日用量</h3><p>GitHub
|
|
13571
|
-
|
|
13645
|
+
<header><div><h3 id="${showWorks ? "platform-usage-calendar-title" : "work-usage-calendar-title"}">每日用量</h3><p>GitHub 风格网格按服务器时区(${esc(usageTimezone)})分年展示;颜色越深,当天消耗越高。</p></div>${calendarYearSelect}</header>
|
|
13646
|
+
<div data-usage-calendar-host>${calendarMarkup}</div>
|
|
13572
13647
|
</section>
|
|
13573
13648
|
${showWorks ? `<section class="usage-work-section" aria-labelledby="usage-work-title"><header><div><h3 id="usage-work-title">各作品用量</h3><p>按 Token 总消耗从高到低排列,包含尚未使用 AI 的作品。</p></div></header><div class="usage-work-table-scroll"><table class="usage-work-table"><thead><tr><th>作品</th><th>总消耗</th><th>输入</th><th>输出</th><th>缓存命中率</th><th>调用</th></tr></thead><tbody>${workRows || '<tr><td colspan="6">还没有作品用量记录。</td></tr>'}</tbody></table></div></section>` : ""}
|
|
13574
13649
|
</section>`;
|
|
@@ -13577,16 +13652,14 @@ function tokenUsageOverviewMarkup(usage, { title, description, showWorks = false
|
|
|
13577
13652
|
async function renderPlatformTokenUsage() {
|
|
13578
13653
|
const host = $("#platform-usage-content");
|
|
13579
13654
|
host.innerHTML = '<div class="empty-state">正在汇总 Token 用量……</div>';
|
|
13580
|
-
const
|
|
13581
|
-
const usage = await api(`/api/platform/ai/usage?timezoneOffset=${timezoneOffset}`);
|
|
13655
|
+
const usage = await api("/api/platform/ai/usage");
|
|
13582
13656
|
host.innerHTML = tokenUsageOverviewMarkup(usage, {
|
|
13583
13657
|
title: "项目累计用量",
|
|
13584
13658
|
description: "汇总所有作品迄今产生的输入与输出 Token;缓存命中率仅基于供应商返回了缓存明细的调用。",
|
|
13585
13659
|
showWorks: true
|
|
13586
13660
|
});
|
|
13587
13661
|
bindTokenUsageDetails(host, usage, "项目累计用量");
|
|
13588
|
-
|
|
13589
|
-
scrollUsageCalendarsToLatest(host);
|
|
13662
|
+
bindUsageCalendar(host, usage);
|
|
13590
13663
|
}
|
|
13591
13664
|
|
|
13592
13665
|
async function renderBookAiSettings() {
|
|
@@ -13606,7 +13679,7 @@ async function renderBookAiSettings() {
|
|
|
13606
13679
|
moduleApi("ai-settings", `/api/works/${state.work.id}/task-defaults`),
|
|
13607
13680
|
moduleApi("ai-settings", `/api/works/${state.work.id}/ai-settings/relationship-search-index`),
|
|
13608
13681
|
moduleApi("ai-settings", `/api/works/${state.work.id}/ai-settings/semantic-search-index`),
|
|
13609
|
-
moduleApi("ai-settings", `/api/works/${state.work.id}/ai-settings/usage
|
|
13682
|
+
moduleApi("ai-settings", `/api/works/${state.work.id}/ai-settings/usage`),
|
|
13610
13683
|
moduleApi("ai-settings", "/api/platform/ai/protocols"),
|
|
13611
13684
|
// 可写工具开关独立于 ai-settings 存储;加载失败时仍可展示其余配置。
|
|
13612
13685
|
api(`/api/works/${state.work.id}/ai/tools`).catch(() => null),
|
|
@@ -13681,8 +13754,7 @@ async function renderBookAiSettings() {
|
|
|
13681
13754
|
if (section.querySelector("h2")?.textContent === "Agent 工具调用上限") section.id = "agent-tool-call-limit-settings";
|
|
13682
13755
|
});
|
|
13683
13756
|
host.insertAdjacentHTML("beforeend", `<section class="config-section"><div class="config-section-header"><div><h2>AI 可写工具</h2><p>默认全部关闭:逐项开启后,侧边栏 AI 才能在对应模块提交修改计划。计划只包含操作描述与 AI 简述;确认前系统会按当前数据库生成字段级明细(含修改前后值),执行时整体原子完成并再次校验权限、开关与目标版本,全程可在「AI 操作审批中心」追溯。AI 不能删除任何条目,也不能改写正文。</p></div><div class="card-actions"><button id="open-ai-approval-center-from-settings" class="ghost-button" type="button">打开 AI 操作审批中心</button></div></div><div class="ai-agent-tools ai-write-tools">${AI_WRITE_TOOLS_META.map((tool) => `<label><input name="ai-write-tool" type="checkbox" value="${esc(tool.id)}" ${writeToolsState?.[tool.id] === true ? "checked" : ""}><span><strong>${esc(tool.label)}</strong><small>${esc(tool.description)}</small></span></label>`).join("")}</div><p class="usage-measurement-note">${writeTools ? `当前单次审批最多 ${writeToolsMaxOperations} 个操作,可通过环境变量 AI_WRITE_PLAN_MAX_OPERATIONS 调整。` : "工具开关状态暂时无法加载,显示的勾选可能不是最新值。"}</p><div class="card-actions"><button id="save-ai-write-tools" class="ghost-button config-save-button" type="button">保存开关设置</button></div></section>`);
|
|
13684
|
-
|
|
13685
|
-
scrollUsageCalendarsToLatest(host);
|
|
13757
|
+
bindUsageCalendar(host, usage);
|
|
13686
13758
|
host.querySelector('input[name="agent-tool"][value="search_story_entities"]').closest("label").insertAdjacentHTML(
|
|
13687
13759
|
"beforebegin",
|
|
13688
13760
|
`<label><input name="agent-tool" type="checkbox" value="grep" ${agentTools.has("grep") ? "checked" : ""}><span><strong>查询正文关键字</strong><small>从段落索引查询关键字,默认返回前 20 条完整段落和章节信息。</small></span></label>`
|
|
@@ -13693,7 +13765,7 @@ async function renderBookAiSettings() {
|
|
|
13693
13765
|
);
|
|
13694
13766
|
host.querySelector('input[name="agent-tool"][value="read_character_sections"]').closest("label").insertAdjacentHTML(
|
|
13695
13767
|
"afterend",
|
|
13696
|
-
`<label><input name="agent-tool" type="checkbox" value="semantic_search_story" ${agentTools.has("semantic_search_story") ? "checked" : ""} ${settings.semanticSearchEnabled ? "" : "disabled"}><span><strong
|
|
13768
|
+
`<label><input name="agent-tool" type="checkbox" value="semantic_search_story" ${agentTools.has("semantic_search_story") ? "checked" : ""} ${settings.semanticSearchEnabled ? "" : "disabled"}><span><strong>语义检索作品原文(RAG)</strong><small>允许 Agent 显式调用 semantic_search_story;只影响保存后新建的对话,普通消息不会自动检索。</small></span></label>`
|
|
13697
13769
|
);
|
|
13698
13770
|
host.querySelector(".ai-agent-tools").insertAdjacentHTML(
|
|
13699
13771
|
"beforeend",
|
|
@@ -18176,7 +18248,7 @@ async function streamChat(requestHolder, body, idempotencyKey) {
|
|
|
18176
18248
|
syncAiTaskOptions();
|
|
18177
18249
|
renderAiRoleplayCharacterSelect();
|
|
18178
18250
|
renderAiQuickActions();
|
|
18179
|
-
clearAiPromptComposer();
|
|
18251
|
+
clearAiPromptComposer({ collapseScenePanel: Boolean(String(body.sceneDirection ?? "").trim()) });
|
|
18180
18252
|
}
|
|
18181
18253
|
mountAssistantMessage();
|
|
18182
18254
|
}
|
|
@@ -18404,7 +18476,7 @@ function appendMessage(role, text, citations = [], createdAt = null, metadata =
|
|
|
18404
18476
|
sceneBody.className = "user-message-scene-body";
|
|
18405
18477
|
sceneBody.textContent = parsedUserTurn.sceneDirection;
|
|
18406
18478
|
scene.append(sceneLabel, sceneBody);
|
|
18407
|
-
message.querySelector(".message-body")?.
|
|
18479
|
+
message.querySelector(".message-body")?.prepend(scene);
|
|
18408
18480
|
}
|
|
18409
18481
|
const mentionGroups = role === "user"
|
|
18410
18482
|
? [
|
|
@@ -20333,7 +20405,9 @@ $("#module-create-button").addEventListener("click", () => ({ drafts: openDraftD
|
|
|
20333
20405
|
$("#ai-prompt").addEventListener("input", async () => {
|
|
20334
20406
|
updateAiMentionMenu();
|
|
20335
20407
|
setAiContextMeter(null);
|
|
20336
|
-
|
|
20408
|
+
const textBeforeCursor = aiPromptTextBeforeCursor();
|
|
20409
|
+
if ($("#ai-task").value !== "roleplay" && findAiSkillCommand(textBeforeCursor)) return;
|
|
20410
|
+
if (!findAiMention(textBeforeCursor)) return;
|
|
20337
20411
|
try {
|
|
20338
20412
|
await ensureAiReferencesLoaded();
|
|
20339
20413
|
updateAiMentionMenu();
|
|
@@ -20463,6 +20537,7 @@ $("#ai-task").addEventListener("change", async (event) => {
|
|
|
20463
20537
|
}
|
|
20464
20538
|
}
|
|
20465
20539
|
setAiContextMeter(null);
|
|
20540
|
+
updateAiMentionMenu();
|
|
20466
20541
|
});
|
|
20467
20542
|
$("#ai-scope").addEventListener("change", (event) => {
|
|
20468
20543
|
if (state.aiPromptSent) {
|
|
@@ -20473,6 +20548,8 @@ $("#ai-scope").addEventListener("change", (event) => {
|
|
|
20473
20548
|
setAiContextMeter(null);
|
|
20474
20549
|
});
|
|
20475
20550
|
$("#ai-mention-menu").addEventListener("click", (event) => {
|
|
20551
|
+
const skillButton = event.target.closest("[data-ai-skill-name]");
|
|
20552
|
+
if (skillButton) return selectAiSkill(skillButton);
|
|
20476
20553
|
const button = event.target.closest("[data-ai-reference-id]");
|
|
20477
20554
|
if (button) selectAiMention(button);
|
|
20478
20555
|
});
|
|
@@ -21016,7 +21093,8 @@ $("#ai-prompt").addEventListener("keydown", (event) => {
|
|
|
21016
21093
|
const activeOption = $("#ai-mention-menu").querySelector('[role="option"][aria-selected="true"]');
|
|
21017
21094
|
if (activeOption) {
|
|
21018
21095
|
event.preventDefault();
|
|
21019
|
-
|
|
21096
|
+
if (activeOption.dataset.aiSkillName) selectAiSkill(activeOption);
|
|
21097
|
+
else selectAiMention(activeOption);
|
|
21020
21098
|
return;
|
|
21021
21099
|
}
|
|
21022
21100
|
}
|
package/dist/public/index.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<script src="/theme-init.js?v=20260827-reader-prefetch-v1"></script>
|
|
10
10
|
<link rel="icon" href="/icon.svg?v=20260712" type="image/svg+xml">
|
|
11
11
|
<link rel="manifest" href="/site.webmanifest">
|
|
12
|
-
<link rel="stylesheet" href="/styles.css?v=20260816-task-scope-volume-collapse-v2&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v3&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=galaxy-compact-controls-v2&feature=galaxy-motion-mode-v2&feature=chapter-search-replace-v3&feature=task-auto-run-ring-center-v3&feature=character-relationship-delete-v1&feature=ai-assistant-workspace-v2&feature=mobile-module-tab-position-v1&feature=volume-detail-icon-v1&feature=editor-actions-flow-v1&feature=reader-controls-subpanel-v1&feature=reader-focus-ring-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v2&feature=ai-composer-square-controls-v2&feature=annotation-line-counts-v1&feature=chapter-comment-filters-v1&feature=line-number-gutter-fill-v1&feature=ai-relationship-roleplay-v1&feature=ai-model-picker-v1&feature=markdown-word-count-five-digit-v2&feature=ai-stream-character-count-stable-v1&feature=ai-stream-character-count-five-digit-v1&feature=annotation-marker-offset-v1&feature=mobile-ai-entry-hidden-v1&feature=phone-client-entry-v1&feature=ai-stream-idle-timeout-v1&feature=ai-user-message-width-v2&feature=ai-chat-image-attachments-v9&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=toast-click-dismiss-v3&feature=character-avatar-v6&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-token-quota-positive-v5&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-stream-connection-seconds-v1&feature=ai-token-usage-estimated-price-v1&feature=record-favorites-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v1&feature=admin-account-identity-v2&feature=task-detail-failure-orange-v1&feature=character-card-header-alignment-v6&feature=character-card-title-fit-v2&feature=book-import-progress-v1&feature=editor-toolbar-compact-v2&feature=reader-first-frame-v1&feature=auth-compact-v2&feature=editor-preview-toggle-v2&feature=setting-title-chrome-v2&feature=entity-pin-icon-center-v1&feature=chapter-center-bottom-space-v1&feature=work-editor-preferences-v1&feature=ai-roleplay-memory-v2&feature=ai-roleplay-memory-v3&feature=ai-roleplay-memory-v5&feature=character-editor-header-actions-v1&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-header-toolbar-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=roleplay-memory-pin-border-v1&feature=ai-write-tools-v2&feature=ai-write-card-actions-compact-v1&feature=ai-write-plan-actions-footer-v1&feature=ai-question-actions-footer-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-answer-limit-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v1&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2">
|
|
12
|
+
<link rel="stylesheet" href="/styles.css?v=20260816-task-scope-volume-collapse-v2&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v3&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=galaxy-compact-controls-v2&feature=galaxy-motion-mode-v2&feature=chapter-search-replace-v3&feature=task-auto-run-ring-center-v3&feature=character-relationship-delete-v1&feature=ai-assistant-workspace-v2&feature=mobile-module-tab-position-v1&feature=volume-detail-icon-v1&feature=editor-actions-flow-v1&feature=reader-controls-subpanel-v1&feature=reader-focus-ring-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v2&feature=ai-composer-square-controls-v2&feature=annotation-line-counts-v1&feature=chapter-comment-filters-v1&feature=line-number-gutter-fill-v1&feature=ai-relationship-roleplay-v1&feature=ai-model-picker-v1&feature=markdown-word-count-five-digit-v2&feature=ai-stream-character-count-stable-v1&feature=ai-stream-character-count-five-digit-v1&feature=annotation-marker-offset-v1&feature=mobile-ai-entry-hidden-v1&feature=phone-client-entry-v1&feature=ai-stream-idle-timeout-v1&feature=ai-user-message-width-v2&feature=ai-chat-image-attachments-v9&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=toast-click-dismiss-v3&feature=character-avatar-v6&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-token-quota-positive-v5&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-stream-connection-seconds-v1&feature=ai-token-usage-estimated-price-v1&feature=record-favorites-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v1&feature=admin-account-identity-v2&feature=task-detail-failure-orange-v1&feature=character-card-header-alignment-v6&feature=character-card-title-fit-v2&feature=book-import-progress-v1&feature=editor-toolbar-compact-v2&feature=reader-first-frame-v1&feature=auth-compact-v2&feature=editor-preview-toggle-v2&feature=setting-title-chrome-v2&feature=entity-pin-icon-center-v1&feature=chapter-center-bottom-space-v1&feature=work-editor-preferences-v1&feature=ai-roleplay-memory-v2&feature=ai-roleplay-memory-v3&feature=ai-roleplay-memory-v5&feature=character-editor-header-actions-v1&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-header-toolbar-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=roleplay-memory-pin-border-v1&feature=ai-write-tools-v2&feature=ai-write-card-actions-compact-v1&feature=ai-write-plan-actions-footer-v1&feature=ai-question-actions-footer-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-answer-limit-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v1&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=ai-settings-textarea-font-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=ai-skill-slash-menu-v1">
|
|
13
13
|
</head>
|
|
14
14
|
<body class="auth-pending">
|
|
15
15
|
<section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
|
|
@@ -351,7 +351,7 @@
|
|
|
351
351
|
<div id="ai-feed" class="ai-feed" role="tabpanel">
|
|
352
352
|
<div class="assistant-message">
|
|
353
353
|
<span>助手</span>
|
|
354
|
-
<div class="message-body"><p
|
|
354
|
+
<div class="message-body"><p>选择章节和模型后即可开始问答;所有引用都基于已保存正文。</p></div>
|
|
355
355
|
</div>
|
|
356
356
|
</div>
|
|
357
357
|
</div>
|
|
@@ -1322,6 +1322,6 @@
|
|
|
1322
1322
|
</dialog>
|
|
1323
1323
|
|
|
1324
1324
|
<div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
|
|
1325
|
-
<script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v2&feature=analysis-task-queue-refresh-v1&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=ai-session-id-copy-v2&feature=galaxy-motion-mode-v3&feature=galaxy-edge-label-threshold-v1&feature=calculate-time-tool-v2&feature=analysis-task-expired-toast-v1&feature=global-replace-volume-v1&feature=chapter-search-replace-v1&feature=chapter-save-toast-v1&feature=character-relationship-delete-v1&feature=character-relationship-group-v1&feature=analysis-task-stability-delay-v1&feature=ai-assistant-workspace-v2&feature=volume-detail-icon-v1&feature=volume-story-order-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-v1&feature=ai-roleplay-message-reference-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v3&feature=annotation-permissions-v1&feature=annotation-line-counts-v1&feature=ai-relationship-roleplay-v1&feature=ai-roleplay-user-character-visibility-v1&feature=ai-roleplay-story-recall-v1&feature=ai-model-picker-v1&feature=ai-fork-model-unlock-v1&feature=context-percent-format-v1&feature=annotation-precise-locate-v1&feature=markdown-word-count-stable-v1&feature=ai-stream-character-count-stable-v2&feature=ai-process-empty-intermediate-v1&feature=ai-feed-scroll-follow-v1&feature=ai-message-retry-v1&feature=ai-stream-idle-timeout-v2&feature=ai-config-delete-v1&feature=ai-provider-protocol-options-v1&feature=ai-provider-thinking-type-v1&feature=ai-chat-image-attachments-v8&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=ai-image-conversation-model-lock-v1&feature=toast-click-dismiss-v2&feature=system-restart-dialog-delay-v1&feature=character-avatar-v6&feature=character-death-position-v1&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-usage-pricing-label-v1&feature=ai-usage-token-breakdown-v1&feature=ai-usage-pricing-cache-v2&feature=ai-usage-pricing-manual-refresh-v1&feature=ai-monthly-token-quota-v1&feature=ai-provider-token-quota-v1&feature=ai-token-quota-positive-v6&feature=ai-model-thinking-label-v1&feature=ai-model-picker-focus-v1&feature=ai-provider-model-import-v1&feature=ai-assistant-brain-icon-v1&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-token-usage-raw-input-v1&feature=ai-stream-connection-seconds-v1&feature=phone-client-entry-v1&feature=ai-usage-pricing-cache-v1&feature=ai-model-thinking-label-v3&feature=ai-roleplay-speaker-label-v1&feature=toast-modal-host-v1&feature=api-key-copy-existing-v1&feature=character-favorite-v1&feature=record-favorites-v1&feature=ai-token-usage-estimated-price-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=entity-pin-icon-v1&feature=roleplay-favorite-label-v1&feature=ai-roleplay-knowledge-tools-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v2&feature=admin-account-identity-v2&feature=ai-provider-analysis-timeout-v1&feature=task-detail-failure-orange-v1&feature=book-import-progress-v1&feature=presence-multiple-users-v1&feature=ai-context-input-output-v1&feature=editor-blank-lines-preserved-v1&feature=reader-first-frame-v1&feature=vditor-lazy-load-v1&feature=ui-module-preload-v1&feature=reader-initial-prefetch-v1&feature=editor-preview-toggle-v2&feature=vditor-fullscreen-disabled-v1&feature=chapter-auto-indent-v1&feature=chapter-centered-scroll-v1&feature=work-editor-preferences-v1&feature=ai-context-output-usage-v1&feature=ai-roleplay-memory-v4&feature=ai-roleplay-memory-v5&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-header-toolbar-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=annotation-line-anchor-v1&feature=stable-line-ids-v1&feature=live-annotation-anchors-v1&feature=chapter-comment-filters-v1&feature=ai-write-tools-v3&feature=ai-question-option-supplement-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-continuation-ui-v1&feature=ai-question-tool-result-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-answer-limit-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v3&feature=ai-cancel-preserve-process-v2&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=chapter-batch-editor-refresh-v1"></script>
|
|
1325
|
+
<script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v2&feature=analysis-task-queue-refresh-v1&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=ai-session-id-copy-v2&feature=galaxy-motion-mode-v3&feature=galaxy-edge-label-threshold-v1&feature=calculate-time-tool-v2&feature=analysis-task-expired-toast-v1&feature=global-replace-volume-v1&feature=chapter-search-replace-v1&feature=chapter-save-toast-v1&feature=character-relationship-delete-v1&feature=character-relationship-group-v1&feature=analysis-task-stability-delay-v1&feature=ai-assistant-workspace-v2&feature=volume-detail-icon-v1&feature=volume-story-order-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-v1&feature=ai-roleplay-message-reference-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v3&feature=annotation-permissions-v1&feature=annotation-line-counts-v1&feature=ai-relationship-roleplay-v1&feature=ai-roleplay-user-character-visibility-v1&feature=ai-roleplay-story-recall-v1&feature=ai-model-picker-v1&feature=ai-fork-model-unlock-v1&feature=context-percent-format-v1&feature=annotation-precise-locate-v1&feature=markdown-word-count-stable-v1&feature=ai-stream-character-count-stable-v2&feature=ai-process-empty-intermediate-v1&feature=ai-feed-scroll-follow-v1&feature=ai-message-retry-v1&feature=ai-stream-idle-timeout-v2&feature=ai-config-delete-v1&feature=ai-provider-protocol-options-v1&feature=ai-provider-thinking-type-v1&feature=ai-chat-image-attachments-v8&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=ai-image-conversation-model-lock-v1&feature=toast-click-dismiss-v2&feature=system-restart-dialog-delay-v1&feature=character-avatar-v6&feature=character-death-position-v1&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-usage-pricing-label-v1&feature=ai-usage-token-breakdown-v1&feature=ai-usage-pricing-cache-v2&feature=ai-usage-pricing-manual-refresh-v1&feature=ai-monthly-token-quota-v1&feature=ai-provider-token-quota-v1&feature=ai-token-quota-positive-v6&feature=ai-model-thinking-label-v1&feature=ai-model-picker-focus-v1&feature=ai-provider-model-import-v1&feature=ai-assistant-brain-icon-v1&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-token-usage-raw-input-v1&feature=ai-stream-connection-seconds-v1&feature=phone-client-entry-v1&feature=ai-usage-pricing-cache-v1&feature=ai-model-thinking-label-v3&feature=ai-roleplay-speaker-label-v1&feature=toast-modal-host-v1&feature=api-key-copy-existing-v1&feature=character-favorite-v1&feature=record-favorites-v1&feature=ai-token-usage-estimated-price-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=entity-pin-icon-v1&feature=roleplay-favorite-label-v1&feature=ai-roleplay-knowledge-tools-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v2&feature=admin-account-identity-v2&feature=ai-provider-analysis-timeout-v1&feature=task-detail-failure-orange-v1&feature=book-import-progress-v1&feature=presence-multiple-users-v1&feature=ai-context-input-output-v1&feature=editor-blank-lines-preserved-v1&feature=reader-first-frame-v1&feature=vditor-lazy-load-v1&feature=ui-module-preload-v1&feature=reader-initial-prefetch-v1&feature=editor-preview-toggle-v2&feature=vditor-fullscreen-disabled-v1&feature=chapter-auto-indent-v1&feature=chapter-centered-scroll-v1&feature=work-editor-preferences-v1&feature=ai-context-output-usage-v1&feature=ai-roleplay-memory-v4&feature=ai-roleplay-memory-v5&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-header-toolbar-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=annotation-line-anchor-v1&feature=stable-line-ids-v1&feature=live-annotation-anchors-v1&feature=chapter-comment-filters-v1&feature=ai-write-tools-v3&feature=ai-question-option-supplement-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-continuation-ui-v1&feature=ai-question-tool-result-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-answer-limit-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v3&feature=ai-cancel-preserve-process-v2&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=chapter-batch-editor-refresh-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=semantic-search-rag-label-v1&feature=ai-skill-slash-menu-v1&feature=ai-roleplay-scene-bubble-v1&feature=ai-roleplay-scene-collapse-v1&feature=markdown-adjacent-blockquotes-v1"></script>
|
|
1326
1326
|
</body>
|
|
1327
1327
|
</html>
|
package/dist/public/markdown.js
CHANGED
|
@@ -201,6 +201,10 @@ export function renderMarkdown(value) {
|
|
|
201
201
|
quote.push(quoteLine[1]);
|
|
202
202
|
continue;
|
|
203
203
|
}
|
|
204
|
+
if (!line.trim() && quote && /^>\s?/u.test(lines[lineIndex + 1] ?? "")) {
|
|
205
|
+
quote.push("");
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
204
208
|
flushQuote();
|
|
205
209
|
if (!line.trim()) {
|
|
206
210
|
// CommonMark 松散列表:同类型列表项之间的空行不结束列表,
|